Passed
Push — master ( dac4dd...1dee1e )
by Petr
02:23
created

ProcessDir::moveDir()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7.0671

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 18
c 1
b 0
f 0
nc 8
nop 2
dl 0
loc 35
rs 8.8333
ccs 16
cts 18
cp 0.8889
crap 7.0671
1
<?php
2
3
namespace kalanis\kw_files\Processing\Volume;
4
5
6
use FilesystemIterator;
7
use kalanis\kw_files\FilesException;
8
use kalanis\kw_files\Interfaces\IFLTranslations;
9
use kalanis\kw_files\Interfaces\IProcessDirs;
10
use kalanis\kw_files\Interfaces\ITypes;
11
use kalanis\kw_files\Node;
12
use kalanis\kw_files\Processing\TPath;
13
use kalanis\kw_files\Traits\TLang;
14
use kalanis\kw_files\Traits\TSubPart;
15
use kalanis\kw_paths\ArrayPath;
16
use kalanis\kw_paths\Extras\TPathTransform;
17
use kalanis\kw_paths\PathsException;
18
use kalanis\kw_storage\Extras\TRemoveCycle;
19
use kalanis\kw_storage\Extras\TVolumeCopy;
20
use RecursiveDirectoryIterator;
21
use RecursiveIteratorIterator;
22
use SplFileInfo;
23
use Throwable;
24
25
26
/**
27
 * Class ProcessDir
28
 * @package kalanis\kw_files\Processing\Volume
29
 * Process dirs in basic ways
30
 */
31
class ProcessDir implements IProcessDirs
32
{
33
    use TLang;
34
    use TPath;
35
    use TPathTransform;
36
    use TRemoveCycle;
37
    use TSubPart;
38
    use TVolumeCopy;
39
40
    /** @var int */
41
    protected $sliceStartParts = 0;
42
    /** @var int */
43
    protected $sliceCustomParts = 0;
44
45
    /**
46
     * @param string $path
47
     * @param IFLTranslations|null $lang
48
     * @throws PathsException
49
     */
50 23
    public function __construct(string $path = '', ?IFLTranslations $lang = null)
51
    {
52 23
        $this->setFlLang($lang);
53 23
        $this->setPath($path);
54 23
    }
55
56
    /**
57
     * @param string $path
58
     * @throws PathsException
59
     */
60 23
    public function setPath(string $path = ''): void
61
    {
62 23
        $used = realpath($path);
63 23
        if (false !== $used) {
64 22
            $this->path = $used;
65 22
            $this->sliceStartParts = count($this->expandName($used));
66
        }
67 23
    }
68
69 4
    public function createDir(array $entry, bool $deep = false): bool
70
    {
71 4
        $path = $this->fullPath($entry);
72
        try {
73 4
            return @mkdir($path, 0777, $deep);
74
            // @codeCoverageIgnoreStart
75
        } catch (Throwable $ex) {
76
            throw new FilesException($this->getFlLang()->flCannotCreateDir($path), $ex->getCode(), $ex);
77
        }
78
        // @codeCoverageIgnoreEnd
79
    }
80
81 4
    public function readDir(array $entry, bool $loadRecursive = false, bool $wantSize = false): array
82
    {
83 4
        $path = $this->fullPath($entry);
84 4
        $this->sliceCustomParts = count($entry);
85 4
        if (!is_dir($path)) {
86 1
            throw new FilesException($this->getFlLang()->flCannotReadDir($path));
87
        }
88
89
        try {
90 3
            $iter = $loadRecursive
91 1
                ? new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path))
92 3
                : new FilesystemIterator($path, 0)
93
            ;
94 3
            return array_values(
95 3
                array_merge(
96 3
                    $this->addRootNode($path, $loadRecursive),
97 3
                    array_map(
98 3
                        [$this, 'intoNode'],
99 3
                        array_filter(
100 3
                            array_filter(
101 3
                                array_filter(
102 3
                                    iterator_to_array($iter)
103
                                ),
104 3
                                [$this, 'filterFilesAndDirsOnly']
105
                            ),
106 3
                            [$this, 'filterDots']
107
                        )
108
                    )
109
                )
110
            );
111
            // @codeCoverageIgnoreStart
112
        } catch (Throwable $ex) {
113
            throw new FilesException($this->getFlLang()->flCannotReadDir($path), $ex->getCode(), $ex);
114
        }
115
        // @codeCoverageIgnoreEnd
116
    }
117
118
    /**
119
     * @param string $path
120
     * @param bool $loadRecursive
121
     * @throws PathsException
122
     * @return Node[]
123
     */
124 3
    protected function addRootNode(string $path, bool $loadRecursive): array
125
    {
126 3
        if (PHP_VERSION_ID >= 80200) {
127
            // these versions pass root node in filesystem iterator implicitly
128
            return [];
129
        }
130 3
        if ($loadRecursive) {
131
            // recursive iterator pass root node implicitly
132 1
            return [];
133
        }
134
        // filesystem iterator in older versions skips root node
135 2
        return [$this->intoNode(new SplFileInfo($path))];
136
    }
137
138 3
    public function filterFilesAndDirsOnly(SplFileInfo $file): bool
139
    {
140 3
        return in_array($file->getType(), [ITypes::TYPE_DIR, ITypes::TYPE_FILE]);
141
    }
142
143 3
    public function filterDots(SplFileInfo $file): bool
144
    {
145 3
        return '..' !== $file->getFilename();
146
    }
147
148
    /**
149
     * @param SplFileInfo $file
150
     * @throws PathsException
151
     * @return Node
152
     */
153 3
    public function intoNode(SplFileInfo $file): Node
154
    {
155 3
        $node = new Node();
156 3
        return $node->setData(
157 3
            array_slice($this->expandName($file->getRealPath()), $this->sliceStartParts + $this->sliceCustomParts),
158 3
            $file->getSize(),
159 3
            $file->getType()
160
        );
161
    }
162
163 5
    public function copyDir(array $source, array $dest): bool
164
    {
165 5
        $src = $this->fullPath($source);
166 5
        $dst = $this->fullPath($dest);
167 5
        if ($this->isSubPart($dest, $source)) {
168 1
            return false;
169
        }
170
171 4
        if (!file_exists($src)) {
172 1
            return false;
173
        }
174
175 3
        $dstArr = new ArrayPath();
176 3
        $dstArr->setArray($dest);
177
178 3
        if (!file_exists($this->fullPath($dstArr->getArrayDirectory()))) {
179 1
            return false;
180
        }
181
182 2
        if (file_exists($dst)) {
183 1
            return false;
184
        }
185
186
        try {
187 1
            return $this->xcopy($src, $dst);
188
            // @codeCoverageIgnoreStart
189
        } catch (Throwable $ex) {
190
            throw new FilesException($this->getFlLang()->flCannotCopyDir($src, $dst), $ex->getCode(), $ex);
191
        }
192
        // @codeCoverageIgnoreEnd
193
    }
194
195 5
    public function moveDir(array $source, array $dest): bool
196
    {
197 5
        $src = $this->fullPath($source);
198 5
        $dst = $this->fullPath($dest);
199 5
        if ($this->isSubPart($dest, $source)) {
200 1
            return false;
201
        }
202
203 4
        if (!file_exists($src)) {
204 1
            return false;
205
        }
206
207 3
        $dstArr = new ArrayPath();
208 3
        $dstArr->setArray($dest);
209
210 3
        if (!file_exists($this->fullPath($dstArr->getArrayDirectory()))) {
211 1
            return false;
212
        }
213
214 2
        if (file_exists($dst)) {
215 1
            return false;
216
        }
217
218
        try {
219
            // original call with doomed sub-calls chmod and chown - see
220
            // @link https://www.php.net/manual/en/function.rename.php#117590
221
            // return @rename($src, $dst);
222
223
            // to avoid that internal bug...
224 1
            $v1 = $this->copyDir($source, $dest);
225 1
            $v2 = $this->deleteDir($source, true);
226 1
            return $v1 && $v2;
227
            // @codeCoverageIgnoreStart
228
        } catch (Throwable $ex) {
229
            throw new FilesException($this->getFlLang()->flCannotMoveDir($src, $dst), $ex->getCode(), $ex);
230
        }
231
        // @codeCoverageIgnoreEnd
232
    }
233
234 6
    public function deleteDir(array $entry, bool $deep = false): bool
235
    {
236 6
        $path = $this->fullPath($entry);
237
        try {
238 6
            if ($deep) {
239 3
                return $this->removeCycle($path);
240
            } else {
241 6
                return @rmdir($path);
242
            }
243
            // @codeCoverageIgnoreStart
244
        } catch (Throwable $ex) {
245
            throw new FilesException($this->getFlLang()->flCannotRemoveDir($path), $ex->getCode(), $ex);
246
        }
247
        // @codeCoverageIgnoreEnd
248
    }
249
250
    /**
251
     * @param array<string> $path
252
     * @throws PathsException
253
     * @return string
254
     */
255 19
    protected function fullPath(array $path): string
256
    {
257 19
        return $this->getPath() . DIRECTORY_SEPARATOR . $this->compactName($path);
258
    }
259
260
    /**
261
     * @return string
262
     * @codeCoverageIgnore only when path fails
263
     */
264
    protected function noDirectoryDelimiterSet(): string
265
    {
266
        return $this->getFlLang()->flNoDirectoryDelimiterSet();
267
    }
268
}
269