Completed
Pull Request — experimental/3.1 (#2644)
by k-yamamura
58:57 queued 33:58
created

PluginService::removeAssets()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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