Completed
Pull Request — 4.0 (#3495)
by k-yamamura
1402:51 queued 1337:41
created

FileController   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 436
Duplicated Lines 6.42 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 87.5%

Importance

Changes 0
Metric Value
dl 28
loc 436
ccs 203
cts 232
cp 0.875
rs 6.96
c 0
b 0
f 0
wmc 53
lcom 1
cbo 14

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
B index() 0 56 5
A view() 0 11 2
B create() 7 48 5
A delete() 0 21 5
A download() 0 28 4
B upload() 7 41 5
A getTreeToArray() 0 16 3
A getPathsToArray() 0 9 2
A getTree() 0 35 5
B getFileList() 0 67 6
A normalizePath() 0 4 1
A checkDir() 0 7 1
A convertStrFromServer() 7 8 2
A convertStrToServer() 7 8 2
A getUserDataDir() 0 4 1
A getJailDir() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FileController 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 FileController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Controller\Admin\Content;
15
16
use Eccube\Controller\AbstractController;
17
use Eccube\Util\FilesystemUtil;
18
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
19
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
20
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
21
use Symfony\Component\Filesystem\Exception\IOException;
22
use Symfony\Component\Filesystem\Filesystem;
23
use Symfony\Component\Finder\Finder;
24
use Symfony\Component\Form\Extension\Core\Type\FileType;
25
use Symfony\Component\Form\Extension\Core\Type\FormType;
26
use Symfony\Component\Form\Extension\Core\Type\TextType;
27
use Symfony\Component\HttpFoundation\BinaryFileResponse;
28
use Symfony\Component\HttpFoundation\File\Exception\FileException;
29
use Symfony\Component\HttpFoundation\Request;
30
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
31
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
32
use Symfony\Component\Validator\Constraints as Assert;
33
34
class FileController extends AbstractController
35
{
36
    const SJIS = 'sjis-win';
37
    const UTF = 'UTF-8';
38
    private $errors = [];
39
    private $encode = '';
40
41
    /**
42
     * FileController constructor.
43
     */
44 6
    public function __construct()
45
    {
46 6
        $this->encode = self::UTF;
47 6
        if ('\\' === DIRECTORY_SEPARATOR) {
48
            $this->encode = self::SJIS;
49
        }
50
    }
51
52
    /**
53
     * @Route("/%eccube_admin_route%/content/file_manager", name="admin_content_file")
54
     * @Template("@admin/Content/file.twig")
55
     */
56 3
    public function index(Request $request)
57
    {
58 3
        $form = $this->formFactory->createBuilder(FormType::class)
59 3
            ->add('file', FileType::class)
60 3
            ->add('create_file', TextType::class)
61 3
            ->getForm();
62
63
        // user_data_dir
64 3
        $userDataDir = $this->getUserDataDir();
65 3
        $topDir = $this->normalizePath($userDataDir);
66
//        $topDir = '/';
67
        // user_data_dirの親ディレクトリ
68 3
        $htmlDir = $this->normalizePath($this->getUserDataDir().'/../');
69
70
        // カレントディレクトリ
71 3
        $nowDir = $this->checkDir($this->getUserDataDir($request->get('tree_select_file')), $this->getUserDataDir())
72 3
            ? $this->normalizePath($this->getUserDataDir($request->get('tree_select_file')))
73 3
            : $topDir;
74
75
        // パンくず表示用データ
76 3
        $nowDirList = json_encode(explode('/', trim(str_replace($htmlDir, '', $nowDir), '/')));
77 3
        $jailNowDir = $this->getJailDir($nowDir);
78 3
        $isTopDir = ($topDir === $jailNowDir);
79 3
        $parentDir = substr($nowDir, 0, strrpos($nowDir, '/'));
80
81 3
        if ('POST' === $request->getMethod()) {
82 2
            switch ($request->get('mode')) {
83
                case 'create':
84 1
                    $this->create($request);
85 1
                    break;
86
                case 'upload':
87 1
                    $this->upload($request);
88 1
                    break;
89
                default:
90
                    break;
91
            }
92
        }
93 3
        $tree = $this->getTree($this->getUserDataDir(), $request);
94 3
        $arrFileList = $this->getFileList($nowDir);
95 3
        $paths = $this->getPathsToArray($tree);
96 3
        $tree = $this->getTreeToArray($tree);
97
98
        return [
99 3
            'form' => $form->createView(),
100 3
            'tpl_javascript' => json_encode($tree),
101 3
            'top_dir' => $this->getJailDir($topDir),
102 3
            'tpl_is_top_dir' => $isTopDir,
103 3
            'tpl_now_dir' => $jailNowDir,
104 3
            'html_dir' => $this->getJailDir($htmlDir),
105 3
            'now_dir_list' => $nowDirList,
106 3
            'tpl_parent_dir' => $this->getJailDir($parentDir),
107 3
            'arrFileList' => $arrFileList,
108 3
            'errors' => $this->errors,
109 3
            'paths' => json_encode($paths),
110
        ];
111
    }
112
113
    /**
114
     * @Route("/%eccube_admin_route%/content/file_view", name="admin_content_file_view")
115
     */
116 1
    public function view(Request $request)
117
    {
118 1
        $file = $this->convertStrToServer($this->getUserDataDir($request->get('file')));
119 1
        if ($this->checkDir($file, $this->getUserDataDir())) {
120 1
            setlocale(LC_ALL, 'ja_JP.UTF-8');
121
122 1
            return new BinaryFileResponse($file);
123
        }
124
125
        throw new NotFoundHttpException();
126
    }
127
128
    /**
129
     * Create directory
130
     *
131
     * @param Request $request
132
     */
133 1
    public function create(Request $request)
134
    {
135 1
        $form = $this->formFactory->createBuilder(FormType::class)
136 1
            ->add('file', FileType::class)
137 1
            ->add('create_file', TextType::class, [
138
                'constraints' => [
139 1
                    new Assert\NotBlank([
140 1
                        'message' => 'file.text.error.folder_name',
141
                    ]),
142 1
                    new Assert\Regex([
143 1
                        'pattern' => '/[^[:alnum:]_.\\-]/',
144
                        'match' => false,
145
                        'message' => 'file.text.error.folder_symbol',
146
                    ]),
147 1
                    new Assert\Regex([
148 1
                        'pattern' => "/^\.(.*)$/",
149
                        'match' => false,
150
                        'message' => 'file.text.error.folder_period',
151
                    ]),
152
                ],
153
            ])
154 1
            ->getForm();
155
156 1
        $form->handleRequest($request);
157 1 View Code Duplication
        if (!$form->isValid()) {
158
            foreach ($form->getErrors(true) as $error) {
159
                $this->errors[] = ['message' => $error->getMessage()];
0 ignored issues
show
Bug introduced by
The method getMessage does only exist in Symfony\Component\Form\FormError, but not in Symfony\Component\Form\FormErrorIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
160
            }
161
162
            return;
163
        }
164
165 1
        $fs = new Filesystem();
166 1
        $filename = $form->get('create_file')->getData();
167
168
        try {
169 1
            $topDir = $this->getUserDataDir();
170 1
            $nowDir = $this->getUserDataDir($request->get('now_dir'));
171 1
            $nowDir = $this->checkDir($nowDir, $topDir)
172
                ? $this->normalizePath($nowDir)
173 1
                : $topDir;
174 1
            $fs->mkdir($nowDir.'/'.$filename);
175
176 1
            $this->addSuccess('admin.content.file.create_dir_success', 'admin');
177
        } catch (IOException $e) {
178
            $this->errors[] = ['message' => $e->getMessage()];
179
        }
180
    }
181
182
    /**
183
     * @Method("DELETE")
184
     * @Route("/%eccube_admin_route%/content/file_delete", name="admin_content_file_delete")
185
     */
186 1
    public function delete(Request $request)
187
    {
188 1
        $this->isTokenValid();
189
190 1
        $selectFile = $request->get('select_file');
191 1
        if (is_null($selectFile) || $selectFile == '/') {
192 1
            return $this->redirectToRoute('admin_content_file');
193 1
        }
194 1
195 1
        $topDir = $this->getUserDataDir();
196 1
        $file = $this->convertStrToServer($this->getUserDataDir($selectFile));
197
        if ($this->checkDir($file, $topDir)) {
198
            $fs = new Filesystem();
199
            if ($fs->exists($file)) {
200 1
                $fs->remove($file);
201
                $this->addSuccess('admin.delete.complete', 'admin');
202
            }
203
        }
204
205
        return $this->redirectToRoute('admin_content_file');
206 1
    }
207
208 1
    /**
209 1
     * @Route("/%eccube_admin_route%/content/file_download", name="admin_content_file_download")
210 1
     */
211 1
    public function download(Request $request)
212 1
    {
213 1
        $topDir = $this->getUserDataDir();
214
        $file = $this->convertStrToServer($this->getUserDataDir($request->get('select_file')));
215
        if ($this->checkDir($file, $topDir)) {
216 1
            if (!is_dir($file)) {
217
                setlocale(LC_ALL, 'ja_JP.UTF-8');
218
                $pathParts = pathinfo($file);
219
220
                $patterns = [
221 1
                    '/[a-zA-Z0-9!"#$%&()=~^|@`:*;+{}]/',
222 1
                    '/[- ,.<>?_[\]\/\\\\]/',
223 1
                    "/['\r\n\t\v\f]/",
224
                ];
225
226
                $str = preg_replace($patterns, '', $pathParts['basename']);
227
                if (strlen($str) === 0) {
228
                    return (new BinaryFileResponse($file))->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
229
                } else {
230
                    return new BinaryFileResponse($file, 200, [
231
                        'Content-Type' => 'aplication/octet-stream;',
232
                        'Content-Disposition' => "attachment; filename*=UTF-8\'\'".rawurlencode($this->convertStrFromServer($pathParts['basename'])),
233
                    ]);
234
                }
235 1
            }
236
        }
237 1
        throw new NotFoundHttpException();
238 1
    }
239
240 1
    public function upload(Request $request)
241 1
    {
242
        $form = $this->formFactory->createBuilder(FormType::class)
243
            ->add('file', FileType::class, [
244
                'constraints' => [
245 1
                    new Assert\NotBlank([
246 1
                        'message' => 'file.text.error.file_not_selected',
247
                    ]),
248 1
                ],
249
            ])
250 1
            ->add('create_file', TextType::class)
251
            ->getForm();
252
253
        $form->handleRequest($request);
254
255 View Code Duplication
        if (!$form->isValid()) {
256
            foreach ($form->getErrors(true) as $error) {
257
                $this->errors[] = ['message' => $error->getMessage()];
0 ignored issues
show
Bug introduced by
The method getMessage does only exist in Symfony\Component\Form\FormError, but not in Symfony\Component\Form\FormErrorIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
258 1
            }
259 1
260 1
            return;
261
        }
262 1
263
        $data = $form->getData();
264
        $topDir = $this->getUserDataDir();
265
        $nowDir = $this->getUserDataDir($request->get('now_dir'));
266
267
        if (!$this->checkDir($nowDir, $topDir)) {
268 1
            $this->errors[] = ['message' => 'file.text.error.invalid_upload_folder'];
269
270 1
            return;
271 1
        }
272
273
        $filename = $this->convertStrToServer($data['file']->getClientOriginalName());
274
        try {
275
            $data['file']->move($nowDir, $filename);
276
            $this->addSuccess('admin.content.file.upload_success', 'admin');
277 3
        } catch (FileException $e) {
278
            $this->errors[] = ['message' => $e->getMessage()];
279 3
        }
280 3
    }
281 3
282 3
    private function getTreeToArray($tree)
283 3
    {
284 3
        $arrTree = [];
285 3
        foreach ($tree as $key => $val) {
286 3
            $path = $this->getJailDir($val['path']);
287 3
            $arrTree[$key] = [
288
                $key,
289
                $val['type'],
290
                $path,
291 3
                $val['depth'],
292
                $val['open'] ? 'true' : 'false',
293
            ];
294 3
        }
295
296 3
        return $arrTree;
297 3
    }
298 3
299
    private function getPathsToArray($tree)
300
    {
301 3
        $paths = [];
302
        foreach ($tree as $val) {
303
            $paths[] = $this->getJailDir($val['path']);
304
        }
305
306
        return $paths;
307
    }
308 3
309
    /**
310 3
     * @param string $topDir
311 3
     * @param Request $request
312 3
     */
313
    private function getTree($topDir, $request)
314 3
    {
315 3
        $finder = Finder::create()->in($topDir)
316 3
            ->directories()
317 3
            ->sortByName();
318 3
319
        $tree = [];
320
        $tree[] = [
321
            'path' => $topDir,
322 3
            'type' => '_parent',
323
            'depth' => 0,
324 3
            'open' => true,
325 3
        ];
326
327
        $defaultDepth = count(explode('/', $topDir));
328
329 3
        $openDirs = [];
330 1
        if ($request->get('tree_status')) {
331 1
            $openDirs = explode('|', $request->get('tree_status'));
332 1
        }
333 1
334 1
        foreach ($finder as $dirs) {
335 1
            $path = $this->normalizePath($dirs->getRealPath());
336 1
            $type = (iterator_count(Finder::create()->in($path)->directories())) ? '_parent' : '_child';
337 1
            $depth = count(explode('/', $path)) - $defaultDepth;
338
            $tree[] = [
339
                'path' => $path,
340
                'type' => $type,
341 3
                'depth' => $depth,
342
                'open' => (in_array($path, $openDirs)) ? true : false,
343
            ];
344
        }
345
346
        return $tree;
347 3
    }
348
349 3
    /**
350 3
     * @param string $nowDir
351 3
     */
352 3
    private function getFileList($nowDir)
353
    {
354 3
        $topDir = $this->getuserDataDir();
355 3
        $filter = function (\SplFileInfo $file) use ($topDir) {
356
            $acceptPath = realpath($topDir);
357 3
            $targetPath = $file->getRealPath();
358 3
359 3
            return strpos($targetPath, $acceptPath) === 0;
360 3
        };
361 3
362 3
        $finder = Finder::create()
363 3
            ->filter($filter)
364
            ->in($nowDir)
365 3
            ->ignoreDotFiles(false)
366
            ->sortByName()
367
            ->depth(0);
368
        $dirFinder = $finder->directories();
369
        try {
370 3
            $dirs = $dirFinder->getIterator();
371
        } catch (\Exception $e) {
372 3
            $dirs = [];
373
        }
374
375
        $fileFinder = $finder->files();
376
        try {
377 3
            $files = $fileFinder->getIterator();
378 3
        } catch (\Exception $e) {
379 1
            $files = [];
380 1
        }
381 1
382 1
        $arrFileList = [];
383 1
        foreach ($dirs as $dir) {
384 1
            $dirPath = $this->normalizePath($dir->getRealPath());
385 1
            $childDir = Finder::create()
386 1
                ->in($dirPath)
387 1
                ->ignoreDotFiles(false)
388 1
                ->directories()
389 1
                ->depth(0);
390 1
            $childFile = Finder::create()
391 1
                ->in($dirPath)
392 1
                ->ignoreDotFiles(false)
393 1
                ->files()
394 1
                ->depth(0);
395 1
            $countNumber = $childDir->count() + $childFile->count();
396
            $arrFileList[] = [
397 1
                'file_name' => $this->convertStrFromServer($dir->getFilename()),
398
                'file_path' => $this->convertStrFromServer($this->getJailDir($dirPath)),
399
                'file_size' => FilesystemUtil::sizeToHumanReadable($dir->getSize()),
400 3
                'file_time' => $dir->getmTime(),
401 3
                'is_dir' => true,
402 3
                'is_empty' => $countNumber == 0 ? true : false,
403 3
            ];
404 3
        }
405 3
        foreach ($files as $file) {
406
            $arrFileList[] = [
407
                'file_name' => $this->convertStrFromServer($file->getFilename()),
408 3
                'file_path' => $this->convertStrFromServer($this->getJailDir($this->normalizePath($file->getRealPath()))),
409
                'file_size' => FilesystemUtil::sizeToHumanReadable($file->getSize()),
410
                'file_time' => $file->getmTime(),
411
                'is_dir' => false,
412 3
                'is_empty' => false,
413
                'extension' => $file->getExtension(),
414
            ];
415 3
        }
416
417 3
        return $arrFileList;
418
    }
419
420
    protected function normalizePath($path)
421
    {
422
        return str_replace('\\', '/', realpath($path));
423 6
    }
424
425 6
    /**
426 6
     * @param string $topDir
427
     */
428 6
    protected function checkDir($targetDir, $topDir)
429
    {
430
        $targetDir = realpath($targetDir);
431
        $topDir = realpath($topDir);
432
433
        return strpos($targetDir, $topDir) === 0;
434 3
    }
435
436 3
    /**
437
     * @return string
438
     */
439 View Code Duplication
    private function convertStrFromServer($target)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
440 3
    {
441
        if ($this->encode == self::SJIS) {
442
            return mb_convert_encoding($target, self::UTF, self::SJIS);
443 4
        }
444
445 4
        return $target;
446
    }
447
448 View Code Duplication
    private function convertStrToServer($target)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
449 4
    {
450
        if ($this->encode == self::SJIS) {
451
            return mb_convert_encoding($target, self::SJIS, self::UTF);
452 6
        }
453
454 6
        return $target;
455
    }
456
457 3
    private function getUserDataDir($nowDir = null)
458
    {
459 3
        return rtrim($this->getParameter('kernel.project_dir').'/html/user_data'.$nowDir, '/');
460 3
    }
461
462 3
    private function getJailDir($path)
463
    {
464
        $realpath = realpath($path);
465
        $jailPath = str_replace(realpath($this->getUserDataDir()), '', $realpath);
466
467
        return $jailPath ? $jailPath : '/';
468
    }
469
}
470