Passed
Pull Request — master (#2)
by
unknown
02:53
created

DocxDocument   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Test Coverage

Coverage 76.25%

Importance

Changes 3
Bugs 1 Features 1
Metric Value
eloc 131
c 3
b 1
f 1
dl 0
loc 278
ccs 106
cts 139
cp 0.7625
rs 6
wmc 55

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 5
F fixTables() 0 103 29
A getDOMDocument() 0 5 1
A getCellLen() 0 17 6
A close() 0 3 1
A save() 0 23 3
A extract() 0 14 3
A rrmdir() 0 15 6
A updateDOMDocument() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like DocxDocument often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocxDocument, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpDocxTemplate;
4
5
use DOMDocument;
6
use DOMElement;
7
use Exception;
8
use PhpZip\ZipFile;
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
     * @throws \PhpZip\Exception\ZipException
56
     */
57 8
    private function extract(): void
58
    {
59 8
        if (file_exists($this->tmpDir) && is_dir($this->tmpDir)) {
60
            $this->rrmdir($this->tmpDir);
61
        }
62
63 8
        mkdir($this->tmpDir);
64
65 8
        $zip = new ZipFile();
66 8
        $zip->openFile($this->path);
67 8
        $zip->extractTo($this->tmpDir);
68 8
        $zip->close();
69
70 8
        $this->document = file_get_contents($this->tmpDir . "/word/document.xml");
71 8
    }
72
73
    /**
74
     * Get document.xml contents as DOMDocument
75
     *
76
     * @return DOMDocument
77
     */
78 5
    public function getDOMDocument(): DOMDocument
79
    {
80 5
        $dom = new DOMDocument();
81 5
        $dom->loadXML($this->document);
82 5
        return $dom;
83
    }
84
85
    /**
86
     * Update document.xml contents
87
     *
88
     * @param DOMDocument $dom - new contents
89
     */
90 3
    public function updateDOMDocument(DOMDocument $dom): void
91
    {
92 3
        $this->document = $dom->saveXml();
93 3
        file_put_contents($this->tmpDir . "/word/document.xml", $this->document);
94 3
    }
95
96
    /**
97
     * Fix table corruption
98
     *
99
     * @param string $xml - xml to fix
100
     *
101
     * @return DOMDocument
102
     */
103 3
    public function fixTables(string $xml): DOMDocument
104
    {
105 3
        $dom = new DOMDocument();
106 3
        $dom->loadXML($xml);
107 3
        $tables = $dom->getElementsByTagName('tbl');
108 3
        foreach ($tables as $table) {
109 1
            $columns = [];
110 1
            $columnsLen = 0;
111 1
            $toAdd = 0;
112 1
            $tableGrid = null;
113 1
            foreach ($table->childNodes as $el) {
114 1
                if ($el->nodeName == 'w:tblGrid') {
115 1
                    $tableGrid = $el;
116 1
                    foreach ($el->childNodes as $col) {
117 1
                        if ($col->nodeName == 'w:gridCol') {
118 1
                            $columns[] = $col;
119 1
                            $columnsLen += 1;
120
                        }
121
                    }
122 1
                } elseif ($el->nodeName == 'w:tr') {
123 1
                    $cellsLen = 0;
124 1
                    foreach ($el->childNodes as $col) {
125 1
                        if ($col->nodeName == 'w:tc') {
126 1
                            $cellsLen += 1;
127
                        }
128
                    }
129 1
                    if (($columnsLen + $toAdd) < $cellsLen) {
130 1
                        $toAdd = $cellsLen - $columnsLen;
131
                    }
132
                }
133
            }
134
135
            // add columns, if necessary
136 1
            if (!is_null($tableGrid) && $toAdd > 0) {
137
                $width = 0;
138
                foreach ($columns as $col) {
139
                    if (!is_null($col->getAttribute('w:w'))) {
140
                        $width += $col->getAttribute('w:w');
141
                    }
142
                }
143
                if ($width > 0) {
144
                    $oldAverage = $width / $columnsLen;
145
                    $newAverage = round($width / ($columnsLen + $toAdd));
146
                    foreach ($columns as $col) {
147
                        $col->setAttribute('w:w', round($col->getAttribute('w:w') * $newAverage / $oldAverage));
148
                    }
149
                    while ($toAdd > 0) {
150
                        $newCol = $dom->createElement("w:gridCol");
151
                        $newCol->setAttribute('w:w', $newAverage);
152
                        $tableGrid->appendChild($newCol);
153
                        $toAdd -= 1;
154
                    }
155
                }
156
            }
157
158
            // remove columns, if necessary
159 1
            $columns = [];
160 1
            foreach ($tableGrid->childNodes as $col) {
161 1
                if ($col->nodeName == 'w:gridCol') {
162 1
                    $columns[] = $col;
163
                }
164
            }
165 1
            $columnsLen = count($columns);
166
167 1
            $cellsLen = 0;
168 1
            $cellsLenMax = 0;
169 1
            foreach ($table->childNodes as $el) {
170 1
                if ($el->nodeName == 'w:tr') {
171 1
                    $cells = [];
172 1
                    foreach ($el->childNodes as $col) {
173 1
                        if ($col->nodeName == 'w:tc') {
174 1
                            $cells[] = $col;
175
                        }
176
                    }
177 1
                    $cellsLen = $this->getCellLen($cells);
178 1
                    $cellsLenMax = max($cellsLenMax, $cellsLen);
179
                }
180
            }
181 1
            $toRemove = $cellsLen - $cellsLenMax;
182 1
            if ($toRemove > 0) {
183
                $removedWidth = 0.0;
184
                for ($i = $columnsLen - 1; ($i + 1) >= $toRemove; $i -= 1) {
185
                    $extraCol = $columns[$i];
186
                    $removedWidth += $extraCol->getAttribute('w:w');
187
                    $tableGrid->removeChild($extraCol);
188
                }
189
190
                $columnsLeft = [];
191
                foreach ($tableGrid->childNodes as $col) {
192
                    if ($col->nodeName == 'w:gridCol') {
193
                        $columnsLeft[] = $col;
194
                    }
195
                }
196
                $extraSpace = 0;
197
                if (count($columnsLeft) > 0) {
198
                    $extraSpace = $removedWidth / count($columnsLeft);
199
                }
200
                foreach ($columnsLeft as $col) {
201 1
                    $col->setAttribute('w:w', round($col->getAttribute('w:w') + $extraSpace));
202
                }
203
            }
204
        }
205 3
        return $dom;
206
    }
207
208
    /**
209
     * Get total cells length
210
     *
211
     * @param array $cells - cells
212
     *
213
     * @return int
214
     */
215 1
    private function getCellLen(array $cells): int
216
    {
217 1
        $total = 0;
218 1
        foreach ($cells as $cell) {
219 1
            foreach ($cell->childNodes as $tc) {
220 1
                if ($tc->nodeName == 'w:tcPr') {
221 1
                    foreach ($tc->childNodes as $span) {
222 1
                        if ($span->nodeName == 'w:gridSpan') {
223 1
                            $total += intval($span->getAttribute('w:val'));
224 1
                            break;
225
                        }
226
                    }
227 1
                    break;
228
                }
229
            }
230
        }
231 1
        return $total + 1;
232
    }
233
234
    /**
235
     * Save the document to the target path
236
     *
237
     * @param string $path - target path
238
     *
239
     * @throws \PhpZip\Exception\ZipException
240
     */
241 1
    public function save(string $path): void
242
    {
243 1
        $rootPath = realpath($this->tmpDir);
244
245 1
        $zip = new ZipFile();
246
247 1
        $files = new RecursiveIteratorIterator(
248 1
            new RecursiveDirectoryIterator($rootPath),
249 1
            RecursiveIteratorIterator::LEAVES_ONLY
250
        );
251
252 1
        foreach ($files as $name => $file) {
253 1
            if (!$file->isDir()) {
254 1
                $filePath = $file->getRealPath();
255 1
                $relativePath = substr($filePath, strlen($rootPath) + 1);
256 1
                $zip->addFile($filePath, $relativePath);
257
            }
258
        }
259
260 1
        $zip->saveAsFile($path);
261 1
        $zip->close();
262
263 1
        $this->rrmdir($this->tmpDir);
264 1
    }
265
266
    /**
267
     * Remove recursively directory
268
     *
269
     * @param string $dir - target directory
270
     */
271 6
    private function rrmdir(string $dir): void
272
    {
273 6
        $objects = scandir($dir);
274 6
        if (is_array($objects)) {
275 6
            foreach ($objects as $object) {
276 6
                if ($object != "." && $object != "..") {
277 6
                    if (filetype($dir . "/" . $object) == "dir") {
278 6
                        $this->rrmdir($dir . "/" . $object);
279
                    } else {
280 6
                        unlink($dir . "/" . $object);
281
                    }
282
                }
283
            }
284 6
            reset($objects);
285 6
            rmdir($dir);
286
        }
287 6
    }
288
289
    /**
290
     * Close document
291
     */
292 5
    public function close(): void
293
    {
294 5
        $this->rrmdir($this->tmpDir);
295 5
    }
296
}
297