Failed Conditions
Pull Request — 3.0.9-dev (#1439)
by k-yamamura
57:50 queued 28:34
created

PluginService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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