Failed Conditions
Pull Request — experimental/3.1 (#2532)
by Kentaro
34:08 queued 17:05
created

PluginService::calcPluginDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
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 Doctrine\ORM\EntityManager;
28
use Eccube\Annotation\Inject;
29
use Eccube\Annotation\Service;
30
use Eccube\Application;
31
use Eccube\Common\Constant;
32
use Eccube\Entity\Plugin;
33
use Eccube\Exception\PluginException;
34
use Eccube\Plugin\ConfigManager;
35
use Eccube\Plugin\ConfigManager as PluginConfigManager;
36
use Eccube\Repository\PluginEventHandlerRepository;
37
use Eccube\Repository\PluginRepository;
38
use Eccube\Util\Cache;
39
use Eccube\Util\Str;
40
use Symfony\Component\Filesystem\Filesystem;
41
use Symfony\Component\Yaml\Yaml;
42
43
/**
44
 * @Service
45
 */
46
class PluginService
47
{
48
    /**
49
     * @Inject(PluginEventHandlerRepository::class)
50
     * @var PluginEventHandlerRepository
51
     */
52
    protected $pluginEventHandlerRepository;
53
54
    /**
55
     * @Inject("orm.em")
56
     * @var EntityManager
57
     */
58
    protected $entityManager;
59
60
    /**
61
     * @Inject(PluginRepository::class)
62
     * @var PluginRepository
63
     */
64
    protected $pluginRepository;
65
66
    /**
67
     * @Inject("config")
68
     * @var array
69
     */
70
    protected $appConfig;
71
72
    /**
73
     * @Inject(Application::class)
74
     * @var Application
75
     */
76
    protected $app;
77
78
    /**
79
     * @var EntityProxyService
80
     * @Inject(EntityProxyService::class)
81
     */
82
    protected $entityProxyService;
83
84
    /**
85
     * @Inject(SchemaService::class)
86
     * @var SchemaService
87
     */
88
    protected $schemaService;
89
90
    const CONFIG_YML = 'config.yml';
91
    const EVENT_YML = 'event.yml';
92
93 13
    public function install($path, $source = 0)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
94
    {
95 13
        $pluginBaseDir = null;
96 13
        $tmp = null;
97
98
        try {
99 13
            PluginConfigManager::removePluginConfigCache();
100 13
            Cache::clear($this->app, false);
101 13
            $tmp = $this->createTempDir();
102
103 13
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
104 13
            $this->checkPluginArchiveContent($tmp);
105
106 11
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
107 11
            $event = $this->readYml($tmp.'/'.self::EVENT_YML);
108 11
            $this->deleteFile($tmp); // テンポラリのファイルを削除
109
110 11
            $this->checkSamePlugin($config['code']); // 重複していないかチェック
111
112 11
            $pluginBaseDir = $this->calcPluginDir($config['code']);
113 11
            $this->createPluginDir($pluginBaseDir); // 本来の置き場所を作成
114
115 11
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
116
117 11
            $this->registerPlugin($config, $event, $source); // dbにプラグイン登録
118 10
            ConfigManager::writePluginConfigCache();
119 4
        } catch (PluginException $e) {
120 4
            $this->deleteDirs(array($tmp, $pluginBaseDir));
121 4
            throw $e;
122
        } catch (\Exception $e) { // インストーラがどんなExceptionを上げるかわからないので
123
124
            $this->deleteDirs(array($tmp, $pluginBaseDir));
125
            throw $e;
126
        }
127
128 10
        return true;
129
    }
130
131 13
    public function createTempDir()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
132
    {
133 13
        @mkdir($this->appConfig['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...
134 13
        $d = ($this->appConfig['plugin_temp_realdir'].'/'.sha1(Str::random(16)));
135
136 13
        if (!mkdir($d, 0777)) {
137
            throw new PluginException($php_errormsg.$d);
138
        }
139
140 13
        return $d;
141
    }
142
143 4
    public function deleteDirs($arr)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
144
    {
145 4
        foreach ($arr as $dir) {
146 4
            if (file_exists($dir)) {
147 3
                $fs = new Filesystem();
148 4
                $fs->remove($dir);
149
            }
150
        }
151
    }
152
153 13
    public function unpackPluginArchive($archive, $dir)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
154
    {
155 13
        $extension = pathinfo($archive, PATHINFO_EXTENSION);
156
        try {
157 13
            if ($extension == 'zip') {
158
                $zip = new \ZipArchive();
159
                $zip->open($archive);
160
                $zip->extractTo($dir);
161
                $zip->close();
162
            } else {
163 13
                $phar = new \PharData($archive);
164 13
                $phar->extractTo($dir, null, true);
165
            }
166
        } catch (\Exception $e) {
167
            throw new PluginException('アップロードに失敗しました。圧縮ファイルを確認してください。');
168
        }
169
    }
170
171 1065
    public function checkPluginArchiveContent($dir, array $config_cache = array())
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
172
    {
173
        try {
174 1065
            if (!empty($config_cache)) {
175 1065
                $meta = $config_cache;
176
            } else {
177 1065
                $meta = $this->readYml($dir . '/config.yml');
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
178
            }
179
        } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
180
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
181
        }
182
183 1065
        if (!is_array($meta)) {
184 2
            throw new PluginException('config.yml not found or syntax error');
185
        }
186 1065 View Code Duplication
        if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
187
            throw new PluginException('config.yml code empty or invalid_character(\W)');
188
        }
189 1065
        if (!isset($meta['name'])) {
190
            // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
191 1
            throw new PluginException('config.yml name empty');
192
        }
193 1065 View Code Duplication
        if (isset($meta['event']) && !$this->checkSymbolName($meta['event'])) { // eventだけは必須ではない
194
            throw new PluginException('config.yml event empty or invalid_character(\W) ');
195
        }
196 1065
        if (!isset($meta['version'])) {
197
            // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
198
            throw new PluginException('config.yml version invalid_character(\W) ');
199
        }
200 1065
        if (isset($meta['orm.path'])) {
201
            if (!is_array($meta['orm.path'])) {
202
                throw new PluginException('config.yml orm.path invalid_character(\W) ');
203
            }
204
        }
205 1065
        if (isset($meta['service'])) {
206
            if (!is_array($meta['service'])) {
207
                throw new PluginException('config.yml service invalid_character(\W) ');
208
            }
209
        }
210
    }
211
212 14
    public function readYml($yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
213
    {
214 14
        if (file_exists($yml)) {
215 12
            return Yaml::parse(file_get_contents($yml));
216
        }
217
218 11
        return false;
219
    }
220
221 1065
    public function checkSymbolName($string)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
222
    {
223 1065
        return strlen($string) < 256 && preg_match('/^\w+$/', $string);
224
        // plugin_nameやplugin_codeに使える文字のチェック
225
        // a-z A-Z 0-9 _
226
        // ディレクトリ名などに使われれるので厳しめ
227
    }
228
229 11
    public function deleteFile($path)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
230
    {
231 11
        $f = new Filesystem();
232 11
        $f->remove($path);
233
    }
234
235 11
    public function checkSamePlugin($code)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
236
    {
237 11
        $repo = $this->pluginRepository->findOneBy(array('code' => $code));
238 11
        if ($repo) {
239 1
            throw new PluginException('plugin already installed.');
240
        }
241
    }
242
243 11
    public function calcPluginDir($name)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
244
    {
245 11
        return $this->appConfig['plugin_realdir'].'/'.$name;
246
    }
247
248 11
    public function createPluginDir($d)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
249
    {
250 11
        $b = @mkdir($d);
251 11
        if (!$b) {
252
            throw new PluginException($php_errormsg);
253
        }
254
    }
255
256 11
    public function registerPlugin($meta, $event_yml, $source = 0)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
257
    {
258 11
        $em = $this->entityManager;
259 11
        $em->getConnection()->beginTransaction();
260
        try {
261 11
            $p = new \Eccube\Entity\Plugin();
262
            // インストール直後はプラグインは有効にしない
263 11
            $p->setName($meta['name'])
264 11
                ->setEnable(Constant::DISABLED)
265 11
                ->setClassName(isset($meta['event']) ? $meta['event'] : '')
266 11
                ->setVersion($meta['version'])
267 11
                ->setSource($source)
268 11
                ->setCode($meta['code']);
269
270 11
            $em->persist($p);
271 11
            $em->flush();
272
273 11
            if (is_array($event_yml)) {
274 2
                foreach ($event_yml as $event => $handlers) {
275 2
                    foreach ($handlers as $handler) {
276 2
                        if (!$this->checkSymbolName($handler[0])) {
277
                            throw new PluginException('Handler name format error');
278
                        }
279 2
                        $peh = new \Eccube\Entity\PluginEventHandler();
280 2
                        $peh->setPlugin($p)
281 2
                            ->setEvent($event)
282 2
                            ->setHandler($handler[0])
283 2
                            ->setHandlerType($handler[1])
284 2
                            ->setPriority($this->pluginEventHandlerRepository->calcNewPriority($event, $handler[1]));
285 2
                        $em->persist($peh);
286 2
                        $em->flush();
287
                    }
288
                }
289
            }
290
291 11
            $em->persist($p);
292
293 11
            $this->callPluginManagerMethod($meta, 'install');
294
295 10
            $em->flush();
296 10
            $em->getConnection()->commit();
297 1
        } catch (\Exception $e) {
298 1
            $em->getConnection()->rollback();
299 1
            throw new PluginException($e->getMessage());
300
        }
301
302 10
        return $p;
303
    }
304
305 11
    public function callPluginManagerMethod($meta, $method)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
306
    {
307 11
        $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
308 11
        if (class_exists($class)) {
309 3
            $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
310 3
            if (method_exists($installer, $method)) {
311 3
                $installer->$method($meta, $this->app);
312
            }
313
        }
314
    }
315
316 5
    public function uninstall(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
317
    {
318 5
        $pluginDir = $this->calcPluginDir($plugin->getCode());
319 5
        ConfigManager::removePluginConfigCache();
320 5
        Cache::clear($this->app, false);
321 5
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'disable');
322 5
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'uninstall');
323 5
        $this->disable($plugin);
324 5
        $this->unregisterPlugin($plugin);
325 5
        $this->deleteFile($pluginDir);
326 5
        ConfigManager::writePluginConfigCache();
327 5
        return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
328
    }
329
330 5
    public function unregisterPlugin(\Eccube\Entity\Plugin $p)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
331
    {
332
        try {
333 5
            $em = $this->entityManager;
334 5
            foreach ($p->getPluginEventHandlers()->toArray() as $peh) {
335 2
                $em->remove($peh);
336
            }
337 5
            $em->remove($p);
338 5
            $em->flush();
339
        } catch (\Exception $e) {
340
            throw $e;
341
        }
342
    }
343
344 6
    public function disable(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
345
    {
346 6
        return $this->enable($plugin, false);
347
    }
348
349
    private function regenerateProxy(Plugin $plugin)
350
    {
351 10
        $enabledPluginEntityDirs = array_map(function($p) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
352 4
            return $this->appConfig['root_dir'].'/app/Plugin/'.$p->getCode().'/Entity';
353 10
        }, $this->pluginRepository->findAllEnabled());
354
355 10
        $entityDir = $this->appConfig['root_dir'].'/app/Plugin/'.$plugin->getCode().'/Entity';
356 10
        if ($plugin->getEnable() === Constant::ENABLED) {
357 7
            $enabledPluginEntityDirs[] = $entityDir;
358
        } else {
359 6
            $index = array_search($entityDir, $enabledPluginEntityDirs);
360 6
            if ($index >=0 ) {
0 ignored issues
show
introduced by
Add a single space around binary operators
Loading history...
Coding Style introduced by
Expected 0 spaces before closing bracket; 1 found
Loading history...
361 6
                array_splice($enabledPluginEntityDirs, $index, 1);
362
            }
363
        }
364
365 10
        return $this->entityProxyService->generate(
366 10
            array_merge([$this->appConfig['root_dir'].'/app/Acme/Entity'], $enabledPluginEntityDirs),
367 10
            $this->appConfig['root_dir'].'/app/proxy/entity'
368
        );
369
    }
370
371 10
    public function enable(\Eccube\Entity\Plugin $plugin, $enable = true)
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
introduced by
Missing function doc comment
Loading history...
372
    {
373 10
        $em = $this->entityManager;
374
        try {
375 10
            PluginConfigManager::removePluginConfigCache();
376 10
            Cache::clear($this->app, false);
377 10
            $pluginDir = $this->calcPluginDir($plugin->getCode());
378 10
            $em->getConnection()->beginTransaction();
379 10
            $plugin->setEnable($enable ? Constant::ENABLED : Constant::DISABLED);
380 10
            $em->persist($plugin);
381
382 10
            $generatedFiles = $this->regenerateProxy($plugin);
383 10
            $this->schemaService->updateSchema($generatedFiles);
384
385 10
            $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), $enable ? 'enable' : 'disable');
386 9
            $em->flush();
387 9
            $em->getConnection()->commit();
388 9
            PluginConfigManager::writePluginConfigCache();
389 1
        } catch (\Exception $e) {
390 1
            $em->getConnection()->rollback();
391 1
            throw $e;
392
        }
393
394 9
        return true;
395
    }
396
397 1
    public function update(\Eccube\Entity\Plugin $plugin, $path)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
398
    {
399 1
        $pluginBaseDir = null;
400 1
        $tmp = null;
401
        try {
402 1
            PluginConfigManager::removePluginConfigCache();
403 1
            Cache::clear($this->app, false);
404 1
            $tmp = $this->createTempDir();
405
406 1
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
407 1
            $this->checkPluginArchiveContent($tmp);
408
409 1
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
410 1
            $event = $this->readYml($tmp.'/event.yml');
411
412 1
            if ($plugin->getCode() != $config['code']) {
413
                throw new PluginException('new/old plugin code is different.');
414
            }
415
416 1
            $pluginBaseDir = $this->calcPluginDir($config['code']);
417 1
            $this->deleteFile($tmp); // テンポラリのファイルを削除
418
419 1
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
420 1
            $this->updatePlugin($plugin, $config, $event); // dbにプラグイン登録
421
422 1
            PluginConfigManager::writePluginConfigCache();
423
        } catch (PluginException $e) {
424
            foreach (array($tmp) as $dir) {
425
                if (file_exists($dir)) {
426
                    $fs = new Filesystem();
427
                    $fs->remove($dir);
428
                }
429
            }
430
            throw $e;
431
        }
432
433 1
        return true;
434
    }
435
436 1
    public function updatePlugin(\Eccube\Entity\Plugin $plugin, $meta, $event_yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
437
    {
438
        try {
439 1
            $em = $this->entityManager;
440 1
            $em->getConnection()->beginTransaction();
441 1
            $plugin->setVersion($meta['version'])
442 1
                ->setName($meta['name']);
443
444 1
            if (isset($meta['event'])) {
445 1
                $plugin->setClassName($meta['event']);
446
            }
447
448 1
            $rep = $this->pluginEventHandlerRepository;
449
450 1
            if (is_array($event_yml)) {
451 1
                foreach ($event_yml as $event => $handlers) {
452 1
                    foreach ($handlers as $handler) {
453 1
                        if (!$this->checkSymbolName($handler[0])) {
454
                            throw new PluginException('Handler name format error');
455
                        }
456
                        // updateで追加されたハンドラかどうか調べる
457 1
                        $peh = $rep->findBy(array(
458 1
                            'plugin_id' => $plugin->getId(),
459 1
                            'event' => $event,
460 1
                            'handler' => $handler[0],
461 1
                            '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...
462
463 1
                        if (!$peh) { // 新規にevent.ymlに定義されたハンドラなのでinsertする
0 ignored issues
show
Bug Best Practice introduced by
The expression $peh of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
464 1
                            $peh = new \Eccube\Entity\PluginEventHandler();
465 1
                            $peh->setPlugin($plugin)
466 1
                                ->setEvent($event)
467 1
                                ->setHandler($handler[0])
468 1
                                ->setHandlerType($handler[1])
469 1
                                ->setPriority($rep->calcNewPriority($event, $handler[1]));
470 1
                            $em->persist($peh);
471 1
                            $em->flush();
472
                        }
473
                    }
474
                }
475
476
                # アップデート後の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...
477 1
                foreach ($rep->findBy(array('plugin_id' => $plugin->getId())) as $peh) {
478 1
                    if (!isset($event_yml[$peh->getEvent()])) {
479
                        $em->remove($peh);
480
                        $em->flush();
481
                    } else {
482 1
                        $match = false;
483 1
                        foreach ($event_yml[$peh->getEvent()] as $handler) {
484 1
                            if ($peh->getHandler() == $handler[0] && $peh->getHandlerType() == $handler[1]) {
485 1
                                $match = true;
486
                            }
487
                        }
488 1
                        if (!$match) {
489 1
                            $em->remove($peh);
490 1
                            $em->flush();
491
                        }
492
                    }
493
                }
494
            }
495
496 1
            $em->persist($plugin);
497 1
            $this->callPluginManagerMethod($meta, 'update');
498 1
            $em->flush();
499 1
            $em->getConnection()->commit();
500
        } catch (\Exception $e) {
501
            $em->getConnection()->rollback();
502
            throw $e;
503
        }
504
    }
505
}
506