Failed Conditions
Pull Request — experimental/3.1 (#2636)
by
unknown
79:00 queued 46:49
created

PluginService::parseToComposerCommand()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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