Completed
Push — experimental/3.1 ( 26b8cd...7f7c1e )
by Ryo
157:54 queued 151:41
created

PluginService::enable()   B

Complexity

Conditions 4
Paths 12

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4.0072

Importance

Changes 0
Metric Value
cc 4
eloc 18
nc 12
nop 2
dl 0
loc 26
ccs 12
cts 13
cp 0.9231
crap 4.0072
rs 8.5806
c 0
b 0
f 0
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\Common\Collections\Criteria;
28
use Doctrine\ORM\EntityManager;
29
use Eccube\Annotation\Inject;
30
use Eccube\Annotation\Service;
31
use Eccube\Application;
32
use Eccube\Common\Constant;
33
use Eccube\Entity\Plugin;
34
use Eccube\Exception\PluginException;
35
use Eccube\Plugin\ConfigManager;
36
use Eccube\Plugin\ConfigManager as PluginConfigManager;
37
use Eccube\Repository\PluginEventHandlerRepository;
38
use Eccube\Repository\PluginRepository;
39
use Eccube\Service\Composer\ComposerServiceInterface;
40
use Eccube\Util\CacheUtil;
41
use Eccube\Util\StringUtil;
42
use Symfony\Component\Filesystem\Filesystem;
43
use Symfony\Component\Yaml\Yaml;
44
45
/**
46
 * @Service
47
 */
48
class PluginService
49
{
50
    /**
51
     * @Inject(PluginEventHandlerRepository::class)
52
     * @var PluginEventHandlerRepository
53
     */
54
    protected $pluginEventHandlerRepository;
55
56
    /**
57
     * @Inject("orm.em")
58
     * @var EntityManager
59
     */
60
    protected $entityManager;
61
62
    /**
63
     * @Inject(PluginRepository::class)
64
     * @var PluginRepository
65
     */
66
    protected $pluginRepository;
67
68
    /**
69
     * @Inject("config")
70
     * @var array
71
     */
72
    protected $appConfig;
73
74
    /**
75
     * @Inject(Application::class)
76
     * @var Application
77
     */
78
    protected $app;
79
80
    /**
81
     * @var EntityProxyService
82
     * @Inject(EntityProxyService::class)
83
     */
84
    protected $entityProxyService;
85
86
    /**
87
     * @Inject(SchemaService::class)
88
     * @var SchemaService
89
     */
90
    protected $schemaService;
91
92
    /**
93 15
     * @Inject("eccube.service.composer")
94
     * @var ComposerServiceInterface
95 15
     */
96 15
    protected $composerService;
97
98
    const CONFIG_YML = 'config.yml';
99
    const EVENT_YML = 'event.yml';
100 15
    const VENDOR_NAME = 'ec-cube';
101 15
102
    /**
103
     * Plugin type/library of ec-cube
104 15
     */
105 15
    const ECCUBE_LIBRARY = 1;
106 15
107
    /**
108 15
     * Plugin type/library of other (except ec-cube)
109 15
     */
110
    const OTHER_LIBRARY = 2;
111 13
112 13
    /**
113 13
     * ファイル指定してのプラグインインストール
114
     *
115 13
     * @param string $path   path to tar.gz/zip plugin file
116
     * @param int    $source
117 13
     * @return mixed
118 13
     * @throws PluginException
119
     * @throws \Exception
120 13
     */
121
    public function install($path, $source = 0)
122 13
    {
123
        $pluginBaseDir = null;
124
        $tmp = null;
125 12
        try {
126 12
            // プラグイン配置前に実施する処理
127
            $this->preInstall();
128 12
            $tmp = $this->createTempDir();
129 4
130 4
            // 一旦テンポラリに展開
131 4
            $this->unpackPluginArchive($path, $tmp);
132
            $this->checkPluginArchiveContent($tmp);
133
134
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
135
            $event = $this->readYml($tmp.'/'.self::EVENT_YML);
136 12
            // テンポラリのファイルを削除
137 15
            $this->deleteFile($tmp);
138 12
139
            // 重複していないかチェック
140 15
            $this->checkSamePlugin($config['code']);
141
142
            $pluginBaseDir = $this->calcPluginDir($config['code']);
143
            // 本来の置き場所を作成
144
            $this->createPluginDir($pluginBaseDir);
145
146
            // 問題なければ本当のplugindirへ
147
            $this->unpackPluginArchive($path, $pluginBaseDir);
148 15
149 15
            // Check dependent plugin
150
            // Don't install ec-cube library
151 15
            $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY);
152
            if (!empty($dependents)) {
153
                $package = $this->parseToComposerCommand($dependents);
154
                $this->composerService->execRequire($package);
155 15
            }
156
157
            // プラグイン配置後に実施する処理
158
            $this->postInstall($config, $event, $source);
159
        } catch (PluginException $e) {
160 4
            $this->deleteDirs(array($tmp, $pluginBaseDir));
161 4
            throw $e;
162 3
        } catch (\Exception $e) {
163 4
            // インストーラがどんなExceptionを上げるかわからないので
164
            $this->deleteDirs(array($tmp, $pluginBaseDir));
165
            throw $e;
166
        }
167
168
        return true;
169
    }
170 15
171
    // インストール事前処理
172 15
    public function preInstall()
0 ignored issues
show
introduced by
You must use "/**" style comments for a function comment
Loading history...
173
    {
174
        // キャッシュの削除
175
        PluginConfigManager::removePluginConfigCache();
176
        CacheUtil::clear($this->app, false);
177
    }
178 15
179 15
    // インストール事後処理
180
    public function postInstall($config, $event, $source)
0 ignored issues
show
introduced by
You must use "/**" style comments for a function comment
Loading history...
181
    {
182
        // Proxyのクラスをロードせずにスキーマを更新するために、
183
        // インストール時には一時的なディレクトリにProxyを生成する
184
        $tmpProxyOutputDir = sys_get_temp_dir() . '/proxy_' . StringUtil::random(12);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
185
        @mkdir($tmpProxyOutputDir);
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...
186
187
        try {
188
            // dbにプラグイン登録
189 1067
            $plugin = $this->registerPlugin($config, $event, $source);
190 1067
191
            // インストール時には一時的に利用するProxyを生成してからスキーマを更新する
192 1067
            $generatedFiles = $this->regenerateProxy($plugin, true, $tmpProxyOutputDir);
193
            $this->schemaService->updateSchema($generatedFiles, $tmpProxyOutputDir);
194
195
            ConfigManager::writePluginConfigCache();
196
        } finally {
197
            foreach (glob("${tmpProxyOutputDir}/*") as  $f) {
0 ignored issues
show
Coding Style introduced by
There should be 1 space after "as" as per the coding-style, but found 2.
Loading history...
198 1067
                unlink($f);
199 2
            }
200
            rmdir($tmpProxyOutputDir);
201 1067
        }
202
    }
203
204 1067
    public function createTempDir()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
205
    {
206 1
        @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...
207
        $d = ($this->appConfig['plugin_temp_realdir'].'/'.sha1(StringUtil::random(16)));
208 1067
209
        if (!mkdir($d, 0777)) {
210
            throw new PluginException($php_errormsg.$d);
211 1067
        }
212
213
        return $d;
214
    }
215 1067
216
    public function deleteDirs($arr)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
217
    {
218
        foreach ($arr as $dir) {
219
            if (file_exists($dir)) {
220 1067
                $fs = new Filesystem();
221
                $fs->remove($dir);
222
            }
223
        }
224
    }
225
226
    public function unpackPluginArchive($archive, $dir)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
227
    {
228
        $extension = pathinfo($archive, PATHINFO_EXTENSION);
229 16
        try {
230 14
            if ($extension == 'zip') {
231
                $zip = new \ZipArchive();
232
                $zip->open($archive);
233 13
                $zip->extractTo($dir);
234
                $zip->close();
235
            } else {
236
                $phar = new \PharData($archive);
237
                $phar->extractTo($dir, null, true);
238 1067
            }
239
        } catch (\Exception $e) {
240
            throw new PluginException('アップロードに失敗しました。圧縮ファイルを確認してください。');
241
        }
242
    }
243
244
    public function checkPluginArchiveContent($dir, array $config_cache = array())
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
245
    {
246 13
        try {
247 13
            if (!empty($config_cache)) {
248
                $meta = $config_cache;
249
            } else {
250
                $meta = $this->readYml($dir . '/config.yml');
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
251
            }
252 13
        } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
253 13
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
254 1
        }
255
256
        if (!is_array($meta)) {
257
            throw new PluginException('config.yml not found or syntax error');
258
        }
259 View Code Duplication
        if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
260 13
            throw new PluginException('config.yml code empty or invalid_character(\W)');
261
        }
262
        if (!isset($meta['name'])) {
263
            // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
264
            throw new PluginException('config.yml name empty');
265 13
        }
266 13 View Code Duplication
        if (isset($meta['event']) && !$this->checkSymbolName($meta['event'])) { // eventだけは必須ではない
267
            throw new PluginException('config.yml event empty or invalid_character(\W) ');
268
        }
269
        if (!isset($meta['version'])) {
270
            // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
271
            throw new PluginException('config.yml version invalid_character(\W) ');
272
        }
273 13
        if (isset($meta['orm.path'])) {
274 13
            if (!is_array($meta['orm.path'])) {
275
                throw new PluginException('config.yml orm.path invalid_character(\W) ');
276 13
            }
277
        }
278 13
        if (isset($meta['service'])) {
279 13
            if (!is_array($meta['service'])) {
280 13
                throw new PluginException('config.yml service invalid_character(\W) ');
281 13
            }
282 13
        }
283 13
    }
284
285 13
    public function readYml($yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
286 13
    {
287
        if (file_exists($yml)) {
288 13
            return Yaml::parse(file_get_contents($yml));
289 2
        }
290 2
291 2
        return false;
292
    }
293
294 2
    public function checkSymbolName($string)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
295 2
    {
296 2
        return strlen($string) < 256 && preg_match('/^\w+$/', $string);
297 2
        // plugin_nameやplugin_codeに使える文字のチェック
298 2
        // a-z A-Z 0-9 _
299 2
        // ディレクトリ名などに使われれるので厳しめ
300 2
    }
301 2
302
    public function deleteFile($path)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
303
    {
304
        $f = new Filesystem();
305
        $f->remove($path);
306 13
    }
307
308 13
    public function checkSamePlugin($code)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
309
    {
310 12
        $repo = $this->pluginRepository->findOneBy(array('code' => $code));
311 12
        if ($repo) {
312 1
            throw new PluginException('plugin already installed.');
313 1
        }
314 1
    }
315
316
    public function calcPluginDir($name)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
317 12
    {
318
        return $this->appConfig['plugin_realdir'].'/'.$name;
319
    }
320
321
    public function createPluginDir($d)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
322 13
    {
323 13
        $b = @mkdir($d);
324 3
        if (!$b) {
325 3
            throw new PluginException($php_errormsg);
326 3
        }
327
    }
328
329
    public function registerPlugin($meta, $event_yml, $source = 0)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
330
    {
331
        $em = $this->entityManager;
332
        $em->getConnection()->beginTransaction();
333 7
        try {
334 7
            $p = new \Eccube\Entity\Plugin();
335 7
            // インストール直後はプラグインは有効にしない
336 7
            $p->setName($meta['name'])
337 7
                ->setEnable(false)
338 7
                ->setClassName(isset($meta['event']) ? $meta['event'] : '')
339 7
                ->setVersion($meta['version'])
340 7
                ->setSource($source)
341
                ->setCode($meta['code']);
342
343 7
            $em->persist($p);
344
            $em->flush();
345 7
346 7
            if (is_array($event_yml)) {
347
                foreach ($event_yml as $event => $handlers) {
348
                    foreach ($handlers as $handler) {
349
                        if (!$this->checkSymbolName($handler[0])) {
350
                            throw new PluginException('Handler name format error');
351
                        }
352 7
                        $peh = new \Eccube\Entity\PluginEventHandler();
353 7
                        $peh->setPlugin($p)
354 2
                            ->setEvent($event)
355
                            ->setHandler($handler[0])
356 7
                            ->setHandlerType($handler[1])
357 7
                            ->setPriority($this->pluginEventHandlerRepository->calcNewPriority($event, $handler[1]));
358
                        $em->persist($peh);
359
                        $em->flush();
360
                    }
361
                }
362
            }
363
364
            $em->persist($p);
365 8
366
            $this->callPluginManagerMethod($meta, 'install');
367
368
            $em->flush();
369
            $em->getConnection()->commit();
370
        } catch (\Exception $e) {
371
            $em->getConnection()->rollback();
372
            throw new PluginException($e->getMessage());
373
        }
374
375
        return $p;
376
    }
377 12
378 10
    public function callPluginManagerMethod($meta, $method)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
379
    {
380 12
        $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
381
        if (class_exists($class)) {
382 12
            $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
383
            if (method_exists($installer, $method)) {
384 12
                $installer->$method($meta, $this->app);
385
            }
386
        }
387 12
    }
388 12
389 12
    public function uninstall(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
390
    {
391 8
        $pluginDir = $this->calcPluginDir($plugin->getCode());
392 8
        ConfigManager::removePluginConfigCache();
393 8
        CacheUtil::clear($this->app, false);
394 8
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'disable');
395
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'uninstall');
396
        $this->disable($plugin);
397
        $this->unregisterPlugin($plugin);
398
        $this->deleteFile($pluginDir);
399 12
400 12
        // スキーマを更新する
401
        $this->schemaService->updateSchema([], $this->appConfig['root_dir'].'/app/proxy/entity');
402 12
403 12
        ConfigManager::writePluginConfigCache();
404 12
        return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
405 12
    }
406
407
    public function unregisterPlugin(\Eccube\Entity\Plugin $p)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
408
    {
409
        try {
410
            $em = $this->entityManager;
411 11
            foreach ($p->getPluginEventHandlers()->toArray() as $peh) {
412
                $em->remove($peh);
413 11
            }
414 11
            $em->remove($p);
415 11
            $em->flush();
416 11
        } catch (\Exception $e) {
417 11
            throw $e;
418 11
        }
419
    }
420 11
421
    public function disable(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
422
    {
423 10
        return $this->enable($plugin, false);
424
    }
425 10
426 10
    /**
427 10
     * Proxyを再生成します.
428 1
     * @param Plugin $plugin プラグイン
429 1
     * @param boolean $temporary プラグインが無効状態でも一時的に生成するかどうか
430 1
     * @param string|null $outputDir 出力先
431
     * @return array 生成されたファイルのパス
432
     */
433 10
    private function regenerateProxy(Plugin $plugin, $temporary, $outputDir = null)
434
    {
435
        if (is_null($outputDir)) {
436
            $outputDir = $this->appConfig['root_dir'].'/app/proxy/entity';
437
        }
438 1
        @mkdir($outputDir);
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...
439 1
440
        $enabledPluginCodes = array_map(
441 1
            function($p) { return $p->getCode(); },
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
Coding Style introduced by
Opening brace must be the last content on the line
Loading history...
introduced by
Missing blank line before return statement
Loading history...
442 1
            $this->pluginRepository->findAllEnabled()
443 1
        );
444
445 1
        $excludes = [];
446 1
        if ($temporary || $plugin->isEnable()) {
447
            $enabledPluginCodes[] = $plugin->getCode();
448 1
        } else {
449 1
            $index = array_search($plugin->getCode(), $enabledPluginCodes);
450
            if ($index >= 0) {
451 1
                array_splice($enabledPluginCodes, $index, 1);
452
                $excludes = [$this->appConfig['root_dir']."/app/Plugin/".$plugin->getCode()."/Entity"];
453
            }
454
        }
455 1
456 1
        $enabledPluginEntityDirs = array_map(function($code) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
457
            return $this->appConfig['root_dir']."/app/Plugin/${code}/Entity";
458 1
        }, $enabledPluginCodes);
459 1
460
        return $this->entityProxyService->generate(
461 1
            array_merge([$this->appConfig['root_dir'].'/app/Acme/Entity'], $enabledPluginEntityDirs),
462
            $excludes,
463
            $outputDir
464
        );
465
    }
466
467
    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...
468
    {
469
        $em = $this->entityManager;
470
        try {
471
            PluginConfigManager::removePluginConfigCache();
472 1
            CacheUtil::clear($this->app, false);
473
            $pluginDir = $this->calcPluginDir($plugin->getCode());
474
            $em->getConnection()->beginTransaction();
475
            $plugin->setEnable($enable ? true : false);
476
            $em->persist($plugin);
477
478 1
            $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), $enable ? 'enable' : 'disable');
479 1
480 1
            // Proxyだけ再生成してスキーマは更新しない
481 1
            $this->regenerateProxy($plugin, false);
482
483 1
            $em->flush();
484 1
            $em->getConnection()->commit();
485
            PluginConfigManager::writePluginConfigCache();
486
        } catch (\Exception $e) {
487 1
            $em->getConnection()->rollback();
488
            throw $e;
489 1
        }
490 1
491 1
        return true;
492 1
    }
493
494
    /**
495
     * Update plugin
496 1
     *
497 1
     * @param Plugin $plugin
498 1
     * @param string $path
499 1
     * @return bool
500 1
     * @throws PluginException
501
     * @throws \Exception
502 1
     */
503 1
    public function update(\Eccube\Entity\Plugin $plugin, $path)
504 1
    {
505 1
        $pluginBaseDir = null;
506 1
        $tmp = null;
507 1
        try {
508 1
            PluginConfigManager::removePluginConfigCache();
509 1
            CacheUtil::clear($this->app, false);
510 1
            $tmp = $this->createTempDir();
511
512
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
513
            $this->checkPluginArchiveContent($tmp);
514
515
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
516 1
            $event = $this->readYml($tmp.'/event.yml');
517 1
518
            if ($plugin->getCode() != $config['code']) {
519
                throw new PluginException('new/old plugin code is different.');
520
            }
521 1
522 1
            $pluginBaseDir = $this->calcPluginDir($config['code']);
523 1
            $this->deleteFile($tmp); // テンポラリのファイルを削除
524 1
525
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
526
527 1
            // Check dependent plugin
528 1
            // Don't install ec-cube library
529 1
            $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY);
530
            if (!empty($dependents)) {
531
                $package = $this->parseToComposerCommand($dependents);
532
                $this->composerService->execRequire($package);
533
            }
534
535 1
            $this->updatePlugin($plugin, $config, $event); // dbにプラグイン登録
536 1
            PluginConfigManager::writePluginConfigCache();
537 1
        } catch (PluginException $e) {
538 1
            $this->deleteDirs([$tmp]);
539
            throw $e;
540
        } catch (\Exception $e) {
541
            // catch exception of composer
542
            $this->deleteDirs([$tmp]);
543
            throw $e;
544
        }
545
546
        return true;
547
    }
548
549
    public function updatePlugin(\Eccube\Entity\Plugin $plugin, $meta, $event_yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
550
    {
551
        try {
552
            $em = $this->entityManager;
553
            $em->getConnection()->beginTransaction();
554
            $plugin->setVersion($meta['version'])
555
                ->setName($meta['name']);
556
557
            if (isset($meta['event'])) {
558
                $plugin->setClassName($meta['event']);
559
            }
560
561
            $rep = $this->pluginEventHandlerRepository;
562
563
            if (is_array($event_yml)) {
564
                foreach ($event_yml as $event => $handlers) {
565
                    foreach ($handlers as $handler) {
566
                        if (!$this->checkSymbolName($handler[0])) {
567
                            throw new PluginException('Handler name format error');
568
                        }
569
                        // updateで追加されたハンドラかどうか調べる
570
                        $peh = $rep->findBy(array(
571
                            'plugin_id' => $plugin->getId(),
572
                            'event' => $event,
573
                            'handler' => $handler[0],
574
                            '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...
575
576
                        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...
577
                            $peh = new \Eccube\Entity\PluginEventHandler();
578
                            $peh->setPlugin($plugin)
579
                                ->setEvent($event)
580
                                ->setHandler($handler[0])
581
                                ->setHandlerType($handler[1])
582
                                ->setPriority($rep->calcNewPriority($event, $handler[1]));
583
                            $em->persist($peh);
584
                            $em->flush();
585
                        }
586
                    }
587
                }
588
589
                # アップデート後の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...
590
                foreach ($rep->findBy(array('plugin_id' => $plugin->getId())) as $peh) {
591
                    if (!isset($event_yml[$peh->getEvent()])) {
592
                        $em->remove($peh);
593
                        $em->flush();
594
                    } else {
595
                        $match = false;
596
                        foreach ($event_yml[$peh->getEvent()] as $handler) {
597
                            if ($peh->getHandler() == $handler[0] && $peh->getHandlerType() == $handler[1]) {
598
                                $match = true;
599
                            }
600
                        }
601
                        if (!$match) {
602
                            $em->remove($peh);
603
                            $em->flush();
604
                        }
605
                    }
606
                }
607
            }
608
609
            $em->persist($plugin);
610
            $this->callPluginManagerMethod($meta, 'update');
611
            $em->flush();
612
            $em->getConnection()->commit();
613
        } catch (\Exception $e) {
614
            $em->getConnection()->rollback();
615
            throw $e;
616
        }
617
    }
618
619
    /**
620
     * Do check dependency plugin
621
     *
622
     * @param array $plugins    get from api
623
     * @param array $plugin     format as plugin from api
624
     * @param array $dependents template output
625
     * @return array|mixed
626
     */
627
    public function getDependency($plugins, $plugin, $dependents = array())
628
    {
629
        // Prevent infinity loop
630
        if (empty($dependents)) {
631
            $dependents[] = $plugin;
632
        }
633
634
        // Check dependency
635
        if (!isset($plugin['require']) || empty($plugin['require'])) {
636
            return $dependents;
637
        }
638
639
        $require = $plugin['require'];
640
        // Check dependency
641
        foreach ($require as $pluginName => $version) {
642
            $dependPlugin = $this->buildInfo($plugins, $pluginName);
643
            // Prevent call self
644
            if (!$dependPlugin || $dependPlugin['product_code'] == $plugin['product_code']) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dependPlugin 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...
645
                continue;
646
            }
647
648
            // Check duplicate in dependency
649
            $index = array_search($dependPlugin['product_code'], array_column($dependents, 'product_code'));
650
            if ($index === false) {
651
                $dependents[] = $dependPlugin;
652
                // Check child dependency
653
                $dependents = $this->getDependency($plugins, $dependPlugin, $dependents);
654
            }
655
        }
656
657
        return $dependents;
658
    }
659
660
    /**
661
     * Get plugin information
662
     *
663
     * @param array  $plugins    get from api
664
     * @param string $pluginCode
665
     * @return array|null
666
     */
667
    public function buildInfo($plugins, $pluginCode)
668
    {
669
        $plugin = [];
670
        $index = $this->checkPluginExist($plugins, $pluginCode);
671
        if ($index === false) {
672
            return $plugin;
673
        }
674
        // Get target plugin in return of api
675
        $plugin = $plugins[$index];
676
677
        // Check the eccube version that the plugin supports.
678
        $plugin['is_supported_eccube_version'] = 0;
679
        if (in_array(Constant::VERSION, $plugin['eccube_version'])) {
680
            // Match version
681
            $plugin['is_supported_eccube_version'] = 1;
682
        }
683
684
        $plugin['depend'] = $this->getRequirePluginName($plugins, $plugin);
685
686
        return $plugin;
687
    }
688
689
    /**
690
     * Get dependency name and version only
691
     *
692
     * @param array $plugins get from api
693
     * @param array $plugin  target plugin from api
694
     * @return mixed format [0 => ['name' => pluginName1, 'version' => pluginVersion1], 1 => ['name' => pluginName2, 'version' => pluginVersion2]]
695
     */
696
    public function getRequirePluginName($plugins, $plugin)
697
    {
698
        $depend = [];
699
        if (isset($plugin['require']) && !empty($plugin['require'])) {
700
            foreach ($plugin['require'] as $name => $version) {
701
                $ret = $this->checkPluginExist($plugins, $name);
702
                if ($ret === false) {
703
                    continue;
704
                }
705
                $depend[] = [
706
                    'name' => $plugins[$ret]['name'],
707
                    'version' => $version,
708
                ];
709
            }
710
        }
711
712
        return $depend;
713
    }
714
715
    /**
716
     * Check require plugin in enable
717
     *
718
     * @param string $pluginCode
719
     * @return array plugin code
720
     */
721
    public function findRequirePluginNeedEnable($pluginCode)
722
    {
723
        $dir = $this->appConfig['plugin_realdir'].'/'.$pluginCode;
724
        $composerFile = $dir.'/composer.json';
725
        $requires = [];
726
        if (!file_exists($composerFile)) {
727
            return $requires;
728
        }
729
        $jsonText = file_get_contents($composerFile);
730
        if ($jsonText) {
731
            $json = json_decode($jsonText, true);
732
            $require = $json['require'];
733
734
            // Remove vendor plugin
735
            if (isset($require[self::VENDOR_NAME.'/plugin-installer'])) {
736
                unset($require[self::VENDOR_NAME.'/plugin-installer']);
737
            }
738
            foreach ($require as $name => $version) {
739
                // Check plugin of ec-cube only
740
                if (strpos($name, self::VENDOR_NAME.'/') !== false) {
741
                    $requireCode = str_replace(self::VENDOR_NAME.'/', '', $name);
742
                    $ret = $this->isEnable($requireCode);
743
                    if ($ret) {
744
                        continue;
745
                    }
746
                    $requires[] = $requireCode;
747
                }
748
            }
749
        }
750
751
        return $requires;
752
    }
753
    /**
754
     * Find the dependent plugins that need to be disabled
755
     *
756
     * @param string $pluginCode
757
     * @return array plugin code
758
     */
759
    public function findDependentPluginNeedDisable($pluginCode)
760
    {
761
        return $this->findDependentPlugin($pluginCode, true);
762
    }
763
764
    /**
765
     * Find the other plugin that has requires on it.
766
     * Check in both dtb_plugin table and <PluginCode>/composer.json
767
     *
768
     * @param string $pluginCode
769
     * @param bool   $enableOnly
770
     * @return array plugin code
771
     */
772
    public function findDependentPlugin($pluginCode, $enableOnly = false)
773
    {
774
        $criteria = Criteria::create()
775
            ->where(Criteria::expr()->neq('code', $pluginCode));
776
        if ($enableOnly) {
777
            $criteria->andWhere(Criteria::expr()->eq('enable', Constant::ENABLED));
778
        }
779
        /**
780
         * @var Plugin[] $plugins
781
         */
782
        $plugins = $this->pluginRepository->matching($criteria);
783
        $dependents = [];
784
        foreach ($plugins as $plugin) {
785
            $dir = $this->appConfig['plugin_realdir'].'/'.$plugin->getCode();
786
            $fileName = $dir.'/composer.json';
787
            if (!file_exists($fileName)) {
788
                continue;
789
            }
790
            $jsonText = file_get_contents($fileName);
791
            if ($jsonText) {
792
                $json = json_decode($jsonText, true);
793
                if (!isset($json['require'])) {
794
                    continue;
795
                }
796
                if (array_key_exists(self::VENDOR_NAME.'/'.$pluginCode, $json['require'])) {
797
                    $dependents[] = $plugin->getCode();
798
                }
799
            }
800
        }
801
802
        return $dependents;
803
    }
804
805
    /**
806
     * Get dependent plugin by code
807
     * It's base on composer.json
808
     * Return the plugin code and version in the format of the composer
809
     *
810
     * @param string   $pluginCode
811
     * @param int|null $libraryType
812
     *                      self::ECCUBE_LIBRARY only return library/plugin of eccube
813
     *                      self::OTHER_LIBRARY only return library/plugin of 3rd part ex: symfony, composer, ...
814
     *                      default : return all library/plugin
815
     * @return array format [packageName1 => version1, packageName2 => version2]
816
     */
817
    public function getDependentByCode($pluginCode, $libraryType = null)
818
    {
819
        $pluginDir = $this->calcPluginDir($pluginCode);
820
        $jsonFile = $pluginDir.'/composer.json';
821
        if (!file_exists($jsonFile)) {
822
            return [];
823
        }
824
        $jsonText = file_get_contents($jsonFile);
825
        $json = json_decode($jsonText, true);
826
        $dependents = [];
827
        if (isset($json['require'])) {
828
            $require = $json['require'];
829
            switch ($libraryType) {
830 View Code Duplication
                case self::ECCUBE_LIBRARY:
831
                    $dependents = array_intersect_key($require, array_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i', array_keys($require))));
832
                    break;
833
834 View Code Duplication
                case self::OTHER_LIBRARY:
835
                    $dependents = array_intersect_key($require, array_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i', array_keys($require), PREG_GREP_INVERT)));
836
                    break;
837
838
                default:
839
                    $dependents = $json['require'];
840
                    break;
841
            }
842
        }
843
844
        return $dependents;
845
    }
846
847
    /**
848
     * Format array dependent plugin to string
849
     * It is used for commands.
850
     *
851
     * @param array $packages   format [packageName1 => version1, packageName2 => version2]
852
     * @param bool  $getVersion
853
     * @return string format if version=true: "packageName1:version1 packageName2:version2", if version=false: "packageName1 packageName2"
854
     */
855
    public function parseToComposerCommand(array $packages, $getVersion = true)
856
    {
857
        $result = array_keys($packages);
858
        if ($getVersion) {
859
            $result = array_map(function ($package, $version) {
860
                return $package.':'.$version;
861
            }, array_keys($packages), array_values($packages));
862
        }
863
864
        return implode(' ', $result);
865
    }
866
867
    /**
868
     * @param string $pluginVersion
869
     * @param string $remoteVersion
870
     * @return mixed
871
     */
872
    public function isUpdate($pluginVersion, $remoteVersion)
873
    {
874
        return version_compare($pluginVersion, $remoteVersion, '<');
875
    }
876
877
    /**
878
     * @param array  $plugins get from api
879
     * @param string $pluginCode
880
     * @return false|int|string
881
     */
882
    private function checkPluginExist($plugins, $pluginCode)
883
    {
884
        if (strpos($pluginCode, self::VENDOR_NAME.'/') !== false) {
885
            $pluginCode = str_replace(self::VENDOR_NAME.'/', '', $pluginCode);
886
        }
887
        // Find plugin in array
888
        $index = array_search($pluginCode, array_column($plugins, 'product_code'));
889
890
        return $index;
891
    }
892
893
    /**
894
     * @param string $code
895
     * @return bool
896
     */
897
    private function isEnable($code)
898
    {
899
        $Plugin = $this->pluginRepository->findOneBy([
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
900
            'enable' => Constant::ENABLED,
901
            'code' => $code
902
        ]);
903
        if ($Plugin) {
904
            return true;
905
        }
906
907
        return false;
908
    }
909
}
910