Completed
Push — sf/csv-headers ( 829ce9...de2c5f )
by Kiyotaka
06:12
created

FileController   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 436
Duplicated Lines 6.42 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
dl 28
loc 436
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
    public function __construct()
45
    {
46
        $this->encode = self::UTF;
47
        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
    public function index(Request $request)
57
    {
58
        $form = $this->formFactory->createBuilder(FormType::class)
59
            ->add('file', FileType::class)
60
            ->add('create_file', TextType::class)
61
            ->getForm();
62
63
        // user_data_dir
64
        $userDataDir = $this->getUserDataDir();
65
        $topDir = $this->normalizePath($userDataDir);
66
//        $topDir = '/';
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
67
        // user_data_dirの親ディレクトリ
68
        $htmlDir = $this->normalizePath($this->getUserDataDir().'/../');
69
70
        // カレントディレクトリ
71
        $nowDir = $this->checkDir($this->getUserDataDir($request->get('tree_select_file')), $this->getUserDataDir())
72
            ? $this->normalizePath($this->getUserDataDir($request->get('tree_select_file')))
73
            : $topDir;
74
75
        // パンくず表示用データ
76
        $nowDirList = json_encode(explode('/', trim(str_replace($htmlDir, '', $nowDir), '/')));
77
        $jailNowDir = $this->getJailDir($nowDir);
78
        $isTopDir = ($topDir === $jailNowDir);
79
        $parentDir = substr($nowDir, 0, strrpos($nowDir, '/'));
80
81
        if ('POST' === $request->getMethod()) {
82
            switch ($request->get('mode')) {
83
                case 'create':
84
                    $this->create($request);
85
                    break;
86
                case 'upload':
87
                    $this->upload($request);
88
                    break;
89
                default:
90
                    break;
91
            }
92
        }
93
        $tree = $this->getTree($this->getUserDataDir(), $request);
94
        $arrFileList = $this->getFileList($nowDir);
95
        $paths = $this->getPathsToArray($tree);
96
        $tree = $this->getTreeToArray($tree);
97
98
        return [
99
            'form' => $form->createView(),
100
            'tpl_javascript' => json_encode($tree),
101
            'top_dir' => $this->getJailDir($topDir),
102
            'tpl_is_top_dir' => $isTopDir,
103
            'tpl_now_dir' => $jailNowDir,
104
            'html_dir' => $this->getJailDir($htmlDir),
105
            'now_dir_list' => $nowDirList,
106
            'tpl_parent_dir' => $this->getJailDir($parentDir),
107
            'arrFileList' => $arrFileList,
108
            'errors' => $this->errors,
109
            'paths' => json_encode($paths),
110
        ];
111
    }
112
113
    /**
114
     * @Route("/%eccube_admin_route%/content/file_view", name="admin_content_file_view")
115
     */
116
    public function view(Request $request)
117
    {
118
        $file = $this->convertStrToServer($this->getUserDataDir($request->get('file')));
119
        if ($this->checkDir($file, $this->getUserDataDir())) {
120
            setlocale(LC_ALL, 'ja_JP.UTF-8');
121
122
            return new BinaryFileResponse($file);
123
        }
124
125
        throw new NotFoundHttpException();
126
    }
127
128
    /**
129
     * Create directory
130
     *
131
     * @param Request $request
132
     */
133
    public function create(Request $request)
134
    {
135
        $form = $this->formFactory->createBuilder(FormType::class)
136
            ->add('file', FileType::class)
137
            ->add('create_file', TextType::class, [
138
                'constraints' => [
139
                    new Assert\NotBlank([
140
                        'message' => 'file.text.error.folder_name',
141
                    ]),
142
                    new Assert\Regex([
143
                        'pattern' => '/[^[:alnum:]_.\\-]/',
144
                        'match' => false,
145
                        'message' => 'file.text.error.folder_symbol',
146
                    ]),
147
                    new Assert\Regex([
148
                        'pattern' => "/^\.(.*)$/",
149
                        'match' => false,
150
                        'message' => 'file.text.error.folder_period',
151
                    ]),
152
                ],
153
            ])
154
            ->getForm();
155
156
        $form->handleRequest($request);
157 View Code Duplication
        if (!$form->isValid()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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
        $fs = new Filesystem();
166
        $filename = $form->get('create_file')->getData();
167
168
        try {
169
            $topDir = $this->getUserDataDir();
170
            $nowDir = $this->getUserDataDir($request->get('now_dir'));
171
            $nowDir = $this->checkDir($nowDir, $topDir)
172
                ? $this->normalizePath($nowDir)
173
                : $topDir;
174
            $fs->mkdir($nowDir.'/'.$filename);
175
176
            $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
    public function delete(Request $request)
187
    {
188
        $this->isTokenValid();
189
190
        $selectFile = $request->get('select_file');
191
        if (is_null($selectFile) || $selectFile == '/') {
192
            return $this->redirectToRoute('admin_content_file');
193
        }
194
195
        $topDir = $this->getUserDataDir();
196
        $file = $this->convertStrToServer($this->getUserDataDir($selectFile));
197
        if ($this->checkDir($file, $topDir)) {
198
            $fs = new Filesystem();
199
            if ($fs->exists($file)) {
200
                $fs->remove($file);
201
                $this->addSuccess('admin.delete.complete', 'admin');
202
            }
203
        }
204
205
        return $this->redirectToRoute('admin_content_file');
206
    }
207
208
    /**
209
     * @Route("/%eccube_admin_route%/content/file_download", name="admin_content_file_download")
210
     */
211
    public function download(Request $request)
212
    {
213
        $topDir = $this->getUserDataDir();
214
        $file = $this->convertStrToServer($this->getUserDataDir($request->get('select_file')));
215
        if ($this->checkDir($file, $topDir)) {
216
            if (!is_dir($file)) {
217
                setlocale(LC_ALL, 'ja_JP.UTF-8');
218
                $pathParts = pathinfo($file);
219
220
                $patterns = [
221
                    '/[a-zA-Z0-9!"#$%&()=~^|@`:*;+{}]/',
222
                    '/[- ,.<>?_[\]\/\\\\]/',
223
                    "/['\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
            }
236
        }
237
        throw new NotFoundHttpException();
238
    }
239
240
    public function upload(Request $request)
241
    {
242
        $form = $this->formFactory->createBuilder(FormType::class)
243
            ->add('file', FileType::class, [
244
                'constraints' => [
245
                    new Assert\NotBlank([
246
                        'message' => 'file.text.error.file_not_selected',
247
                    ]),
248
                ],
249
            ])
250
            ->add('create_file', TextType::class)
251
            ->getForm();
252
253
        $form->handleRequest($request);
254
255 View Code Duplication
        if (!$form->isValid()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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
            }
259
260
            return;
261
        }
262
263
        $data = $form->getData();
264
        $topDir = $this->getUserDataDir();
265
        $nowDir = $this->getUserDataDir($request->get('now_dir'));
266
267
        if (!$this->checkDir($nowDir, $topDir)) {
268
            $this->errors[] = ['message' => 'file.text.error.invalid_upload_folder'];
269
270
            return;
271
        }
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
        } catch (FileException $e) {
278
            $this->errors[] = ['message' => $e->getMessage()];
279
        }
280
    }
281
282
    private function getTreeToArray($tree)
283
    {
284
        $arrTree = [];
285
        foreach ($tree as $key => $val) {
286
            $path = $this->getJailDir($val['path']);
287
            $arrTree[$key] = [
288
                $key,
289
                $val['type'],
290
                $path,
291
                $val['depth'],
292
                $val['open'] ? 'true' : 'false',
293
            ];
294
        }
295
296
        return $arrTree;
297
    }
298
299
    private function getPathsToArray($tree)
300
    {
301
        $paths = [];
302
        foreach ($tree as $val) {
303
            $paths[] = $this->getJailDir($val['path']);
304
        }
305
306
        return $paths;
307
    }
308
309
    /**
310
     * @param string $topDir
311
     * @param Request $request
312
     */
313
    private function getTree($topDir, $request)
314
    {
315
        $finder = Finder::create()->in($topDir)
316
            ->directories()
317
            ->sortByName();
318
319
        $tree = [];
320
        $tree[] = [
321
            'path' => $topDir,
322
            'type' => '_parent',
323
            'depth' => 0,
324
            'open' => true,
325
        ];
326
327
        $defaultDepth = count(explode('/', $topDir));
328
329
        $openDirs = [];
330
        if ($request->get('tree_status')) {
331
            $openDirs = explode('|', $request->get('tree_status'));
332
        }
333
334
        foreach ($finder as $dirs) {
335
            $path = $this->normalizePath($dirs->getRealPath());
336
            $type = (iterator_count(Finder::create()->in($path)->directories())) ? '_parent' : '_child';
337
            $depth = count(explode('/', $path)) - $defaultDepth;
338
            $tree[] = [
339
                'path' => $path,
340
                'type' => $type,
341
                'depth' => $depth,
342
                'open' => (in_array($path, $openDirs)) ? true : false,
343
            ];
344
        }
345
346
        return $tree;
347
    }
348
349
    /**
350
     * @param string $nowDir
351
     */
352
    private function getFileList($nowDir)
353
    {
354
        $topDir = $this->getuserDataDir();
355
        $filter = function (\SplFileInfo $file) use ($topDir) {
356
            $acceptPath = realpath($topDir);
357
            $targetPath = $file->getRealPath();
358
359
            return strpos($targetPath, $acceptPath) === 0;
360
        };
361
362
        $finder = Finder::create()
363
            ->filter($filter)
364
            ->in($nowDir)
365
            ->ignoreDotFiles(false)
366
            ->sortByName()
367
            ->depth(0);
368
        $dirFinder = $finder->directories();
369
        try {
370
            $dirs = $dirFinder->getIterator();
371
        } catch (\Exception $e) {
372
            $dirs = [];
373
        }
374
375
        $fileFinder = $finder->files();
376
        try {
377
            $files = $fileFinder->getIterator();
378
        } catch (\Exception $e) {
379
            $files = [];
380
        }
381
382
        $arrFileList = [];
383
        foreach ($dirs as $dir) {
384
            $dirPath = $this->normalizePath($dir->getRealPath());
385
            $childDir = Finder::create()
386
                ->in($dirPath)
387
                ->ignoreDotFiles(false)
388
                ->directories()
389
                ->depth(0);
390
            $childFile = Finder::create()
391
                ->in($dirPath)
392
                ->ignoreDotFiles(false)
393
                ->files()
394
                ->depth(0);
395
            $countNumber = $childDir->count() + $childFile->count();
396
            $arrFileList[] = [
397
                'file_name' => $this->convertStrFromServer($dir->getFilename()),
398
                'file_path' => $this->convertStrFromServer($this->getJailDir($dirPath)),
399
                'file_size' => FilesystemUtil::sizeToHumanReadable($dir->getSize()),
400
                'file_time' => $dir->getmTime(),
401
                'is_dir' => true,
402
                'is_empty' => $countNumber == 0 ? true : false,
403
            ];
404
        }
405
        foreach ($files as $file) {
406
            $arrFileList[] = [
407
                'file_name' => $this->convertStrFromServer($file->getFilename()),
408
                '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
                'is_empty' => false,
413
                'extension' => $file->getExtension(),
414
            ];
415
        }
416
417
        return $arrFileList;
418
    }
419
420
    protected function normalizePath($path)
421
    {
422
        return str_replace('\\', '/', realpath($path));
423
    }
424
425
    /**
426
     * @param string $topDir
427
     */
428
    protected function checkDir($targetDir, $topDir)
429
    {
430
        $targetDir = realpath($targetDir);
431
        $topDir = realpath($topDir);
432
433
        return strpos($targetDir, $topDir) === 0;
434
    }
435
436
    /**
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
    {
441
        if ($this->encode == self::SJIS) {
442
            return mb_convert_encoding($target, self::UTF, self::SJIS);
443
        }
444
445
        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
    {
450
        if ($this->encode == self::SJIS) {
451
            return mb_convert_encoding($target, self::SJIS, self::UTF);
452
        }
453
454
        return $target;
455
    }
456
457
    private function getUserDataDir($nowDir = null)
458
    {
459
        return rtrim($this->getParameter('kernel.project_dir').'/html/user_data'.$nowDir, '/');
460
    }
461
462
    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