PluginService   C
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 396
Duplicated Lines 1.52 %

Coupling/Cohesion

Components 1
Dependencies 4
Metric Value
wmc 74
lcom 1
cbo 4
dl 6
loc 396
rs 5.5244

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B install() 0 34 3
A createTempDir() 0 11 2
A deleteDirs() 0 9 3
A unpackPluginArchive() 0 17 3
C checkPluginArchiveContent() 6 36 13
A readYml() 0 8 2
A checkSymbolName() 0 7 2
A deleteFile() 0 5 1
A checkSamePlugin() 0 7 2
A calcPluginDir() 0 4 1
A createPluginDir() 0 7 2
C registerPlugin() 0 50 7
A callPluginManagerMethod() 0 10 3
A uninstall() 0 11 1
A unregisterPlugin() 0 20 3
A disable() 0 4 1
A enable() 0 18 4
B update() 0 38 6
C updatePlugin() 0 70 14

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 PluginService 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 PluginService, 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) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * This program is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU General Public License
12
 * as published by the Free Software Foundation; either version 2
13
 * of the License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
 */
24
25
namespace Eccube\Service;
26
27
use Eccube\Common\Constant;
28
use Eccube\Exception\PluginException;
29
use Eccube\Util\Str;
30
use Symfony\Component\Filesystem\Filesystem;
31
use Symfony\Component\Yaml\Yaml;
32
33
class PluginService
0 ignored issues
show
introduced by
Missing class doc comment
Loading history...
34
{
35
    const CONFIG_YML = 'config.yml';
36
    const EVENT_YML = 'event.yml';
37
    private $app;
38
39
    public function __construct($app)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
40
    {
41
        $this->app = $app;
42
    }
43
44
    public function install($path, $source = 0)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
45
    {
46
        $pluginBaseDir = null;
47
        $tmp = null;
48
49
        try {
50
            $tmp = $this->createTempDir();
51
52
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
53
            $this->checkPluginArchiveContent($tmp);
54
55
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
56
            $event = $this->readYml($tmp.'/'.self::EVENT_YML);
57
            $this->deleteFile($tmp); // テンポラリのファイルを削除
58
59
            $this->checkSamePlugin($config['code']); // 重複していないかチェック
60
61
            $pluginBaseDir = $this->calcPluginDir($config['code']);
62
            $this->createPluginDir($pluginBaseDir); // 本来の置き場所を作成
63
64
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
65
66
            $this->registerPlugin($config, $event, $source); // dbにプラグイン登録
67
        } catch (PluginException $e) {
68
            $this->deleteDirs(array($tmp, $pluginBaseDir));
69
            throw $e;
70
        } catch (\Exception $e) { // インストーラがどんなExceptionを上げるかわからないので
71
72
            $this->deleteDirs(array($tmp, $pluginBaseDir));
73
            throw $e;
74
        }
75
76
        return true;
77
    }
78
79
    public function createTempDir()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
80
    {
81
        @mkdir($this->app['config']['plugin_temp_realdir']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
82
        $d = ($this->app['config']['plugin_temp_realdir'].'/'.sha1(Str::random(16)));
83
84
        if (!mkdir($d, 0777)) {
85
            throw new PluginException($php_errormsg.$d);
86
        }
87
88
        return $d;
89
    }
90
91
    public function deleteDirs($arr)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
92
    {
93
        foreach ($arr as $dir) {
94
            if (file_exists($dir)) {
95
                $fs = new Filesystem();
96
                $fs->remove($dir);
97
            }
98
        }
99
    }
100
101
    public function unpackPluginArchive($archive, $dir)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
102
    {
103
        $extension = pathinfo($archive, PATHINFO_EXTENSION);
104
        try {
105
            if ($extension == 'zip') {
106
                $zip = new \ZipArchive();
107
                $zip->open($archive);
108
                $zip->extractTo($dir);
109
                $zip->close();
110
            } else {
111
                $phar = new \PharData($archive);
112
                $phar->extractTo($dir, null, true);
113
            }
114
        } catch (\Exception $e) {
115
            throw new PluginException('アップロードに失敗しました。圧縮ファイルを確認してください。');
116
        }
117
    }
118
119
    public function checkPluginArchiveContent($dir)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
120
    {
121
        try {
122
            $meta = $this->readYml($dir . '/config.yml');
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
123
        } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
124
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
125
        }
126
127
        if (!is_array($meta)) {
128
            throw new PluginException('config.yml not found or syntax error');
129
        }
130 View Code Duplication
        if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
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...
131
            throw new PluginException('config.yml code empty or invalid_character(\W)');
132
        }
133
        if (!isset($meta['name'])) {
134
            // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
135
            throw new PluginException('config.yml name empty');
136
        }
137 View Code Duplication
        if (isset($meta['event']) && !$this->checkSymbolName($meta['event'])) { // eventだけは必須ではない
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...
138
            throw new PluginException('config.yml event empty or invalid_character(\W) ');
139
        }
140
        if (!isset($meta['version'])) {
141
            // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
142
            throw new PluginException('config.yml version invalid_character(\W) ');
143
        }
144
        if (isset($meta['orm.path'])) {
145
            if (!is_array($meta['orm.path'])) {
146
                throw new PluginException('config.yml orm.path invalid_character(\W) ');
147
            }
148
        }
149
        if (isset($meta['service'])) {
150
            if (!is_array($meta['service'])) {
151
                throw new PluginException('config.yml service invalid_character(\W) ');
152
            }
153
        }
154
    }
155
156
    public function readYml($yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
157
    {
158
        if (file_exists($yml)) {
159
            return Yaml::parse(file_get_contents($yml));
160
        }
161
162
        return false;
163
    }
164
165
    public function checkSymbolName($string)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
166
    {
167
        return strlen($string) < 256 && preg_match('/^\w+$/', $string);
168
        // plugin_nameやplugin_codeに使える文字のチェック
169
        // a-z A-Z 0-9 _
170
        // ディレクトリ名などに使われれるので厳しめ
171
    }
172
173
    public function deleteFile($path)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
174
    {
175
        $f = new Filesystem();
176
        $f->remove($path);
177
    }
178
179
    public function checkSamePlugin($code)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
180
    {
181
        $repo = $this->app['eccube.repository.plugin']->findOneBy(array('code' => $code));
182
        if ($repo) {
183
            throw new PluginException('plugin already installed.');
184
        }
185
    }
186
187
    public function calcPluginDir($name)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
188
    {
189
        return $this->app['config']['plugin_realdir'].'/'.$name;
190
    }
191
192
    public function createPluginDir($d)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
193
    {
194
        $b = @mkdir($d);
195
        if (!$b) {
196
            throw new PluginException($php_errormsg);
197
        }
198
    }
199
200
    public function registerPlugin($meta, $event_yml, $source = 0)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
201
    {
202
        $em = $this->app['orm.em'];
203
        $em->getConnection()->beginTransaction();
204
        try {
205
            $p = new \Eccube\Entity\Plugin();
206
            // インストール直後はプラグインは有効にしない
207
            $p->setName($meta['name'])
208
                ->setEnable(Constant::DISABLED)
209
                ->setClassName(isset($meta['event']) ? $meta['event'] : '')
210
                ->setVersion($meta['version'])
211
                ->setDelflg(Constant::DISABLED)
212
                ->setSource($source)
213
                ->setCode($meta['code']);
214
215
            $em->persist($p);
216
            $em->flush();
217
218
            if (is_array($event_yml)) {
219
                foreach ($event_yml as $event => $handlers) {
220
                    foreach ($handlers as $handler) {
221
                        if (!$this->checkSymbolName($handler[0])) {
222
                            throw new PluginException('Handler name format error');
223
                        }
224
                        $peh = new \Eccube\Entity\PluginEventHandler();
225
                        $peh->setPlugin($p)
226
                            ->setEvent($event)
227
                            ->setdelFlg(Constant::DISABLED)
228
                            ->setHandler($handler[0])
229
                            ->setHandlerType($handler[1])
230
                            ->setPriority($this->app['eccube.repository.plugin_event_handler']->calcNewPriority($event, $handler[1]));
231
                        $em->persist($peh);
232
                        $em->flush();
233
                    }
234
                }
235
            }
236
237
            $em->persist($p);
238
239
            $this->callPluginManagerMethod($meta, 'install');
240
241
            $em->flush();
242
            $em->getConnection()->commit();
243
        } catch (\Exception $e) {
244
            $em->getConnection()->rollback();
245
            throw new PluginException($e->getMessage());
246
        }
247
248
        return $p;
249
    }
250
251
    public function callPluginManagerMethod($meta, $method)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
252
    {
253
        $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
254
        if (class_exists($class)) {
255
            $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
256
            if (method_exists($installer, $method)) {
257
                $installer->$method($meta, $this->app);
258
            }
259
        }
260
    }
261
262
    public function uninstall(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
263
    {
264
        $pluginDir = $this->calcPluginDir($plugin->getCode());
265
266
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'disable');
267
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'uninstall');
268
        $this->unregisterPlugin($plugin);
269
        $this->deleteFile($pluginDir);
270
271
        return true;
272
    }
273
274
    public function unregisterPlugin(\Eccube\Entity\Plugin $p)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
275
    {
276
        try {
277
            $em = $this->app['orm.em'];
278
            $em->getConnection()->beginTransaction();
279
280
            $p->setDelFlg(Constant::ENABLED)->setEnable(Constant::DISABLED);
281
282
            foreach ($p->getPluginEventHandlers()->toArray() as $peh) {
283
                $peh->setDelFlg(Constant::ENABLED);
284
            }
285
286
            $em->persist($p);
287
            $em->flush();
288
            $em->getConnection()->commit();
289
        } catch (\Exception $e) {
290
            $em->getConnection()->rollback();
291
            throw $e;
292
        }
293
    }
294
295
    public function disable(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
296
    {
297
        return $this->enable($plugin, false);
298
    }
299
300
    public function enable(\Eccube\Entity\Plugin $plugin, $enable = true)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
301
    {
302
        $em = $this->app['orm.em'];
303
        try {
304
            $pluginDir = $this->calcPluginDir($plugin->getCode());
305
            $em->getConnection()->beginTransaction();
306
            $plugin->setEnable($enable ? Constant::ENABLED : Constant::DISABLED);
307
            $em->persist($plugin);
308
            $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), $enable ? 'enable' : 'disable');
309
            $em->flush();
310
            $em->getConnection()->commit();
311
        } catch (\Exception $e) {
312
            $em->getConnection()->rollback();
313
            throw $e;
314
        }
315
316
        return true;
317
    }
318
319
    public function update(\Eccube\Entity\Plugin $plugin, $path)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
320
    {
321
        $pluginBaseDir = null;
322
        $tmp = null;
323
        try {
324
            $tmp = $this->createTempDir();
325
326
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
327
            $this->checkPluginArchiveContent($tmp);
328
329
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
330
            $event = $this->readYml($tmp.'/event.yml');
331
332
            if ($plugin->getCode() != $config['code']) {
333
                throw new PluginException('new/old plugin code is different.');
334
            }
335
            if ($plugin->getName() != $config['name']) {
336
                throw new PluginException('new/old plugin name is different.');
337
            }
338
339
            $pluginBaseDir = $this->calcPluginDir($config['code']);
340
            $this->deleteFile($tmp); // テンポラリのファイルを削除
341
342
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
343
344
            $this->updatePlugin($plugin, $config, $event); // dbにプラグイン登録
345
        } catch (PluginException $e) {
346
            foreach (array($tmp) as $dir) {
347
                if (file_exists($dir)) {
348
                    $fs = new Filesystem();
349
                    $fs->remove($dir);
350
                }
351
            }
352
            throw $e;
353
        }
354
355
        return true;
356
    }
357
358
    public function updatePlugin(\Eccube\Entity\Plugin $plugin, $meta, $event_yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
359
    {
360
        try {
361
            $em = $this->app['orm.em'];
362
            $em->getConnection()->beginTransaction();
363
            $plugin->setVersion($meta['version'])
364
                ->setName($meta['name']);
365
366
            if (isset($meta['event'])) {
367
                $plugin->setClassName($meta['event']);
368
            }
369
370
            $rep = $this->app['eccube.repository.plugin_event_handler'];
371
372
            if (is_array($event_yml)) {
373
                foreach ($event_yml as $event => $handlers) {
374
                    foreach ($handlers as $handler) {
375
                        if (!$this->checkSymbolName($handler[0])) {
376
                            throw new PluginException('Handler name format error');
377
                        }
378
                        // updateで追加されたハンドラかどうか調べる
379
                        $peh = $rep->findBy(array('del_flg' => Constant::DISABLED,
380
                            'plugin_id' => $plugin->getId(),
381
                            'event' => $event,
382
                            'handler' => $handler[0],
383
                            'handler_type' => $handler[1],));
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 24 spaces, but found 28.
Loading history...
introduced by
Add a single space after each comma delimiter
Loading history...
384
385
                        if (!$peh) { // 新規にevent.ymlに定義されたハンドラなのでinsertする
386
                            $peh = new \Eccube\Entity\PluginEventHandler();
387
                            $peh->setPlugin($plugin)
388
                                ->setEvent($event)
389
                                ->setdelFlg(Constant::DISABLED)
390
                                ->setHandler($handler[0])
391
                                ->setHandlerType($handler[1])
392
                                ->setPriority($rep->calcNewPriority($event, $handler[1]));
393
                            $em->persist($peh);
394
                            $em->flush();
395
                        }
396
                    }
397
                }
398
399
                # アップデート後のevent.ymlで削除されたハンドラをdtb_plugin_event_handlerから探して削除
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
400
                foreach ($rep->findBy(array('del_flg' => Constant::DISABLED, 'plugin_id' => $plugin->getId())) as $peh) {
401
                    if (!isset($event_yml[$peh->getEvent()])) {
402
                        $em->remove($peh);
403
                        $em->flush();
404
                    } else {
405
                        $match = false;
406
                        foreach ($event_yml[$peh->getEvent()] as $handler) {
407
                            if ($peh->getHandler() == $handler[0] && $peh->getHandlerType() == $handler[1]) {
408
                                $match = true;
409
                            }
410
                        }
411
                        if (!$match) {
412
                            $em->remove($peh);
413
                            $em->flush();
414
                        }
415
                    }
416
                }
417
            }
418
419
            $em->persist($plugin);
420
            $this->callPluginManagerMethod($meta, 'update');
421
            $em->flush();
422
            $em->getConnection()->commit();
423
        } catch (\Exception $e) {
424
            $em->getConnection()->rollback();
425
            throw $e;
426
        }
427
    }
428
}
429