Passed
Push — master ( cdb806...a96575 )
by Bingo
03:52 queued 12s
created

DocxDocument::getCellLen()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 6
eloc 10
c 1
b 0
f 1
nc 6
nop 1
dl 0
loc 17
ccs 11
cts 11
cp 1
crap 6
rs 9.2222
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
     *
28
     * @throws Exception
29
     */
30 6
    public function __construct(string $path)
31
    {
32 6
        if (file_exists($path)) {
33 6
            $this->path = $path;
34 6
            $this->tmpDir .= uniqid(true) . date("His");
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $prefix of uniqid(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

34
            $this->tmpDir .= uniqid(/** @scrutinizer ignore-type */ true) . date("His");
Loading history...
35 6
            $this->extract();
36
        } else {
37
            throw new Exception("The template " . $path . " was not found!");
38
        }
39 6
    }
40
41
    /**
42
     * Extract (unzip) document contents
43
     */
44 6
    private function extract(): void
45
    {
46 6
        if (file_exists($this->tmpDir) && is_dir($this->tmpDir)) {
47
            $this->rrmdir($this->tmpDir);
48
        }
49
        
50 6
        mkdir($this->tmpDir);
51
52 6
        $zip = new ZipArchive();
53 6
        $zip->open($this->path);
54 6
        $zip->extractTo($this->tmpDir);
55 6
        $zip->close();
56
57 6
        $this->document = file_get_contents($this->tmpDir . "/word/document.xml");
58 6
    }
59
60
    /**
61
     * Get document.xml contents as DOMDocument
62
     *
63
     * @return DOMDocument
64
     */
65 3
    public function getDOMDocument(): DOMDocument
66
    {
67 3
        $dom = new DOMDocument();
68 3
        $dom->loadXML($this->document);
69 3
        return $dom;
70
    }
71
72
    /**
73
     * Update document.xml contents
74
     *
75
     * @param DOMDocument $dom - new contents
76
     */
77 1
    public function updateDOMDocument(DOMDocument $dom): void
78
    {
79 1
        $this->document = $dom->saveXml();
80 1
        file_put_contents($this->tmpDir . "/word/document.xml", $this->document);
81 1
    }
82
83
    /**
84
     * Fix table corruption
85
     *
86
     * @param string $xml - xml to fix
87
     *
88
     * @return DOMDocument
89
     */
90 1
    public function fixTables(string $xml): DOMDocument
91
    {
92 1
        $dom = new DOMDocument();
93 1
        $dom->loadXML($xml);
94 1
        $tables = $dom->getElementsByTagName('tbl');
95 1
        foreach ($tables as $table) {
96 1
            foreach ($table->childNodes as $el) {
97 1
                if ($el->nodeName == 'w:tblGrid') {
98 1
                    $tableGrid = $el;
99 1
                    $columns = [];
100 1
                    $columnsLen = 0;
101 1
                    foreach ($el->childNodes as $col) {
102 1
                        if ($col->nodeName == 'w:gridCol') {
103 1
                            $columns[] = $col;
104 1
                            $columnsLen += 1;
105
                        }
106
                    }
107 1
                } elseif ($el->nodeName == 'w:tr') {
108 1
                    $toAdd = 0;
109 1
                    $cellsLen = 0;
110 1
                    foreach ($el->childNodes as $col) {
111 1
                        if ($col->nodeName == 'w:tc') {
112 1
                            $cellsLen += 1;
113
                        }
114
                    }
115 1
                    if (($columnsLen + $toAdd) < $cellsLen) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $columnsLen does not seem to be defined for all execution paths leading up to this point.
Loading history...
116 1
                        $toAdd = $cellsLen - $columnsLen;
117
                    }
118
                }
119
            }
120
121
            // add columns, if necessary
122 1
            if ($toAdd > 0) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $toAdd does not seem to be defined for all execution paths leading up to this point.
Loading history...
123
                $width = 0;
124
                foreach ($columns as $col) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $columns does not seem to be defined for all execution paths leading up to this point.
Loading history...
125
                    if (!is_null($col->getAttribute('w:w'))) {
126
                        $width += $col->getAttribute('w:w');
127
                    }
128
                }
129
                if ($width > 0) {
130
                    $oldAverage = $width / $columnsLen;
131
                    $newAverage = round($width / ($columnsLen + $toAdd));
132
                    foreach ($columns as $col) {
133
                        $col->setAttribute('w:w', round($col->getAttribute('w:w') * $newAverage / $oldAverage));
134
                    }
135
                    while ($toAdd > 0) {
136
                        $newCol = $dom->createElement("w:gridCol");
137
                        $newCol->setAttribute('w:w', $newAverage);
138
                        $tableGrid->appendChild($newCol);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tableGrid does not seem to be defined for all execution paths leading up to this point.
Loading history...
139
                        $toAdd -= 1;
140
                    }
141
                }
142
            }
143
144
            // remove columns, if necessary
145 1
            $columns = [];
146 1
            foreach ($tableGrid->childNodes as $col) {
147 1
                if ($col->nodeName == 'w:gridCol') {
148 1
                    $columns[] = $col;
149
                }
150
            }
151 1
            $columnsLen = count($columns);
152 1
            $cellsLenMax = 0;
153
            
154 1
            foreach ($table->childNodes as $el) {
155 1
                if ($el->nodeName == 'w:tr') {
156 1
                    $cells = [];
157 1
                    foreach ($el->childNodes as $col) {
158 1
                        if ($col->nodeName == 'w:tc') {
159 1
                            $cells[] = $col;
160
                        }
161
                    }
162 1
                    $cellsLen = $this->getCellLen($cells);
163 1
                    $cellsLenMax = max($cellsLenMax, $cellsLen);
164
                }
165
            }
166 1
            $toRemove = $cellsLen - $cellsLenMax;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cellsLen does not seem to be defined for all execution paths leading up to this point.
Loading history...
167 1
            if ($toRemove > 0) {
168
                $removedWidth = 0.0;
169
                for ($i = $columnsLen - 1; ($i + 1) >= $toRemove; $i -= 1) {
170
                    $extraCol = $columns[$i];
171
                    $removedWidth += $extraCol->getAttribute('w:w');
172
                    $tableGrid->removeChild($extraCol);
173
                }
174
175
                $columnsLeft = [];
176
                foreach ($tableGrid->childNodes as $col) {
177
                    if ($col->nodeName == 'w:gridCol') {
178
                        $columnsLeft[] = $col;
179
                    }
180
                }
181
                $extraSpace = 0;
182
                if (count($columnsLeft) > 0) {
183
                    $extraSpace = $removedWidth / count($columnsLeft);
184
                }
185
                foreach ($columnsLeft as $col) {
186 1
                    $col->setAttribute('w:w', round($col->getAttribute('w:w') + $extraSpace));
187
                }
188
            }
189
        }
190 1
        return $dom;
191
    }
192
193
    /**
194
     * Get total cells length
195
     *
196
     * @param array $cells - cells
197
     *
198
     * @return int
199
     */
200 1
    private function getCellLen(array $cells): int
201
    {
202 1
        $total = 0;
203 1
        foreach ($cells as $cell) {
204 1
            foreach ($cell->childNodes as $tc) {
205 1
                if ($tc->nodeName == 'w:tcPr') {
206 1
                    foreach ($tc->childNodes as $span) {
207 1
                        if ($span->nodeName == 'w:gridSpan') {
208 1
                            $total += intval($span->getAttribute('w:val'));
209 1
                            break;
210
                        }
211
                    }
212 1
                    break;
213
                }
214
            }
215
        }
216 1
        return $total + 1;
217
    }
218
219
    /**
220
     * Save the document to the target path
221
     *
222
     * @param string $path - target path
223
     */
224 1
    public function save(string $path): void
225
    {
226 1
        $rootPath = realpath($this->tmpDir);
227
228 1
        $zip = new ZipArchive();
229 1
        $zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE);
230
231 1
        $files = new RecursiveIteratorIterator(
232 1
            new RecursiveDirectoryIterator($rootPath),
233 1
            RecursiveIteratorIterator::LEAVES_ONLY
234
        );
235
236 1
        foreach ($files as $name => $file) {
237 1
            if (!$file->isDir()) {
238 1
                $filePath = $file->getRealPath();
239 1
                $relativePath = substr($filePath, strlen($rootPath) + 1);
240 1
                $zip->addFile($filePath, $relativePath);
241
            }
242
        }
243
244 1
        $zip->close();
245
246 1
        $this->rrmdir($this->tmpDir);
247 1
    }
248
249
    /**
250
     * Remove recursively directory
251
     *
252
     * @param string $dir - target directory
253
     */
254 6
    private function rrmdir(string $dir): void
255
    {
256 6
        if (is_dir($dir)) {
257 6
            $objects = scandir($dir);
258 6
            foreach ($objects as $object) {
259 6
                if ($object != "." && $object != "..") {
260 6
                    if (filetype($dir . "/" . $object) == "dir") {
261 6
                        $this->rrmdir($dir . "/" . $object);
262
                    } else {
263 6
                        unlink($dir . "/" . $object);
264
                    }
265
                }
266
            }
267 6
            reset($objects);
0 ignored issues
show
Bug introduced by
It seems like $objects can also be of type false; however, parameter $array of reset() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

267
            reset(/** @scrutinizer ignore-type */ $objects);
Loading history...
268 6
            rmdir($dir);
269
        }
270 6
    }
271
272
    /**
273
     * Close document
274
     */
275 5
    public function close(): void
276
    {
277 5
        $this->rrmdir($this->tmpDir);
278 5
    }
279
}
280