Failed Conditions
Push — sf/composer-json ( 664b6a...e61037 )
by Kiyotaka
06:12
created

PluginService::checkSamePlugin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Service;
15
16
use Doctrine\Common\Collections\Criteria;
17
use Doctrine\ORM\EntityManager;
18
use Doctrine\ORM\EntityManagerInterface;
19
use Eccube\Application;
20
use Eccube\Common\Constant;
21
use Eccube\Common\EccubeConfig;
22
use Eccube\Entity\Plugin;
23
use Eccube\Exception\PluginException;
24
use Eccube\Repository\PluginRepository;
25
use Eccube\Service\Composer\ComposerServiceInterface;
26
use Eccube\Util\CacheUtil;
27
use Eccube\Util\StringUtil;
28
use Symfony\Component\DependencyInjection\ContainerInterface;
29
use Symfony\Component\Filesystem\Filesystem;
30
use Symfony\Component\Yaml\Yaml;
31
32
class PluginService
33
{
34
    /**
35
     * @var EccubeConfig
36
     */
37
    protected $eccubeConfig;
38
39
    /**
40
     * @var EntityManager
41
     */
42
    protected $entityManager;
43
44
    /**
45
     * @var PluginRepository
46
     */
47
    protected $pluginRepository;
48
49
    /**
50
     * @var Application
51
     */
52
    protected $app;
53
54
    /**
55
     * @var EntityProxyService
56
     */
57
    protected $entityProxyService;
58
59
    /**
60
     * @var SchemaService
61
     */
62
    protected $schemaService;
63
64
    /**
65
     * @var ComposerServiceInterface
66
     */
67
    protected $composerService;
68
69
    const VENDOR_NAME = 'ec-cube';
70
71
    /**
72
     * Plugin type/library of ec-cube
73
     */
74
    const ECCUBE_LIBRARY = 1;
75
76
    /**
77
     * Plugin type/library of other (except ec-cube)
78
     */
79
    const OTHER_LIBRARY = 2;
80
81
    /**
82
     * @var string %kernel.project_dir%
83
     */
84
    private $projectRoot;
85
86
    /**
87
     * @var string %kernel.environment%
88
     */
89
    private $environment;
90
91
    /**
92
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
93
     */
94
    protected $container;
95
96
    /** @var CacheUtil */
97
    protected $cacheUtil;
98
99
    /**
100
     * PluginService constructor.
101
     *
102
     * @param EntityManagerInterface $entityManager
103
     * @param PluginRepository $pluginRepository
104
     * @param EntityProxyService $entityProxyService
105
     * @param SchemaService $schemaService
106
     * @param EccubeConfig $eccubeConfig
107
     * @param ContainerInterface $container
108
     * @param CacheUtil $cacheUtil
109
     */
110
    public function __construct(
111
        EntityManagerInterface $entityManager,
112
        PluginRepository $pluginRepository,
113
        EntityProxyService $entityProxyService,
114
        SchemaService $schemaService,
115
        EccubeConfig $eccubeConfig,
116
        ContainerInterface $container,
117
        CacheUtil $cacheUtil
118
    ) {
119
        $this->entityManager = $entityManager;
0 ignored issues
show
Documentation Bug introduced by
$entityManager is of type object<Doctrine\ORM\EntityManagerInterface>, but the property $entityManager was declared to be of type object<Doctrine\ORM\EntityManager>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
120
        $this->pluginRepository = $pluginRepository;
121
        $this->entityProxyService = $entityProxyService;
122
        $this->schemaService = $schemaService;
123
        $this->eccubeConfig = $eccubeConfig;
124
        $this->projectRoot = $eccubeConfig->get('kernel.project_dir');
125
        $this->environment = $eccubeConfig->get('kernel.environment');
126
        $this->container = $container;
127
        $this->cacheUtil = $cacheUtil;
128
    }
129
130
    /**
131
     * ファイル指定してのプラグインインストール
132
     *
133
     * @param string $path   path to tar.gz/zip plugin file
134
     * @param int    $source
135
     *
136
     * @return boolean
137
     *
138
     * @throws PluginException
139
     * @throws \Exception
140
     */
141
    public function install($path, $source = 0)
142
    {
143
        $pluginBaseDir = null;
144
        $tmp = null;
145
        try {
146
            // プラグイン配置前に実施する処理
147
            $this->preInstall();
148
            $tmp = $this->createTempDir();
149
150
            // 一旦テンポラリに展開
151
            $this->unpackPluginArchive($path, $tmp);
152
            $this->checkPluginArchiveContent($tmp);
153
154
            $config = $this->readConfig($tmp);
155
            // テンポラリのファイルを削除
156
            $this->deleteFile($tmp);
157
158
            // 重複していないかチェック
159
            $this->checkSamePlugin($config['code']);
160
161
            $pluginBaseDir = $this->calcPluginDir($config['code']);
162
            // 本来の置き場所を作成
163
            $this->createPluginDir($pluginBaseDir);
164
165
            // 問題なければ本当のplugindirへ
166
            $this->unpackPluginArchive($path, $pluginBaseDir);
167
168
            // Check dependent plugin
169
            // Don't install ec-cube library
170
//            $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY);
171
//            if (!empty($dependents)) {
172
//                $package = $this->parseToComposerCommand($dependents);
173
            //FIXME: how to working with ComposerProcessService or ComposerApiService ?
174
//                $this->composerService->execRequire($package);
175
//            }
176
177
            // プラグイン配置後に実施する処理
178
            $this->postInstall($config, $source);
179
            // リソースファイルをコピー
180
            $this->copyAssets($pluginBaseDir, $config['code']);
181
        } catch (PluginException $e) {
182
            $this->deleteDirs([$tmp, $pluginBaseDir]);
183
            throw $e;
184
        } catch (\Exception $e) {
185
            // インストーラがどんなExceptionを上げるかわからないので
186
            $this->deleteDirs([$tmp, $pluginBaseDir]);
187
            throw $e;
188
        }
189
190
        return true;
191
    }
192
193
    // インストール事前処理
194
    public function preInstall()
195
    {
196
        // キャッシュの削除
197
        // FIXME: Please fix clearCache function (because it's clear all cache and this file just upload)
198
//        $this->cacheUtil->clearCache();
199
    }
200
201
    // インストール事後処理
202
    public function postInstall($config, $source)
203
    {
204
        // Proxyのクラスをロードせずにスキーマを更新するために、
205
        // インストール時には一時的なディレクトリにProxyを生成する
206
        $tmpProxyOutputDir = sys_get_temp_dir().'/proxy_'.StringUtil::random(12);
207
        @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...
208
209
        try {
210
            // dbにプラグイン登録
211
            $plugin = $this->registerPlugin($config, $source);
212
213
            // プラグインmetadata定義を追加
214
            $entityDir = $this->eccubeConfig['plugin_realdir'].'/'.$plugin->getCode().'/Entity';
215
            if (file_exists($entityDir)) {
216
                $ormConfig = $this->entityManager->getConfiguration();
217
                $chain = $ormConfig->getMetadataDriverImpl();
218
                $driver = $ormConfig->newDefaultAnnotationDriver([$entityDir], false);
219
                $namespace = 'Plugin\\'.$config['code'].'\\Entity';
220
                $chain->addDriver($driver, $namespace);
221
                $ormConfig->addEntityNamespace($plugin->getCode(), $namespace);
222
            }
223
224
            // インストール時には一時的に利用するProxyを生成してからスキーマを更新する
225
            $generatedFiles = $this->regenerateProxy($plugin, true, $tmpProxyOutputDir);
226
            $this->schemaService->updateSchema($generatedFiles, $tmpProxyOutputDir);
227
        } finally {
228
            foreach (glob("${tmpProxyOutputDir}/*") as  $f) {
229
                unlink($f);
230
            }
231
            rmdir($tmpProxyOutputDir);
232
        }
233
    }
234
235
    public function createTempDir()
236
    {
237
        $tempDir = $this->projectRoot.'/var/cache/'.$this->environment.'/Plugin';
238
        @mkdir($tempDir);
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...
239
        $d = ($tempDir.'/'.sha1(StringUtil::random(16)));
240
241
        if (!mkdir($d, 0777)) {
242
            throw new PluginException($php_errormsg.$d);
243
        }
244
245
        return $d;
246
    }
247
248
    public function deleteDirs($arr)
249
    {
250
        foreach ($arr as $dir) {
251
            if (file_exists($dir)) {
252
                $fs = new Filesystem();
253
                $fs->remove($dir);
254
            }
255
        }
256
    }
257
258
    /**
259
     * @param string $archive
260
     * @param string $dir
261
     *
262
     * @throws PluginException
263
     */
264
    public function unpackPluginArchive($archive, $dir)
265
    {
266
        $extension = pathinfo($archive, PATHINFO_EXTENSION);
267
        try {
268
            if ($extension == 'zip') {
269
                $zip = new \ZipArchive();
270
                $zip->open($archive);
271
                $zip->extractTo($dir);
272
                $zip->close();
273
            } else {
274
                $phar = new \PharData($archive);
275
                $phar->extractTo($dir, null, true);
276
            }
277
        } catch (\Exception $e) {
278
            throw new PluginException(trans('pluginservice.text.error.upload_failure'));
279
        }
280
    }
281
282
    /**
283
     * @param $dir
284
     * @param array $config_cache
285
     *
286
     * @throws PluginException
287
     */
288
    public function checkPluginArchiveContent($dir, array $config_cache = [])
289
    {
290
        try {
291
            if (!empty($config_cache)) {
292
                $meta = $config_cache;
293
            } else {
294
                $meta = $this->readConfig($dir);
295
            }
296
        } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
297
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
298
        }
299
300
        if (!is_array($meta)) {
301
            throw new PluginException('config.yml not found or syntax error');
302
        }
303
        if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
0 ignored issues
show
Bug introduced by
The method checkSymbolName() does not seem to exist on object<Eccube\Service\PluginService>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
304
            throw new PluginException('config.yml code empty or invalid_character(\W)');
305
        }
306
        if (!isset($meta['name'])) {
307
            // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
308
            throw new PluginException('config.yml name empty');
309
        }
310
        if (!isset($meta['version'])) {
311
            // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
312
            throw new PluginException('config.yml version invalid_character(\W) ');
313
        }
314
        if (isset($meta['orm.path'])) {
315
            if (!is_array($meta['orm.path'])) {
316
                throw new PluginException('config.yml orm.path invalid_character(\W) ');
317
            }
318
        }
319
        if (isset($meta['service'])) {
320
            if (!is_array($meta['service'])) {
321
                throw new PluginException('config.yml service invalid_character(\W) ');
322
            }
323
        }
324
    }
325
326
    /**
327
     * @param $pluginDir
328
     * @return array
329
     * @throws PluginException
330
     */
331
    public function readConfig($pluginDir) {
332
333
        $composerJsonPath = $pluginDir.DIRECTORY_SEPARATOR.'composer.json';
334
        if (file_exists($composerJsonPath) === false) {
335
            throw new PluginException("${composerJsonPath} not found.");
336
        }
337
338
        $json = json_decode(file_get_contents($composerJsonPath), true);
339
        if ($json === null) {
340
            throw new PluginException("Invalid json format. [${composerJsonPath}]");
341
        }
342
343
        if (!isset($json['version'])) {
344
            throw new PluginException("`version` is not defined in ${composerJsonPath}");
345
        }
346
347
        if (!isset($json['extra']['code'])) {
348
            throw new PluginException("`extra.code` is not defined in ${composerJsonPath}");
349
        }
350
351
        return [
352
            "code" => $json['extra']['code'],
353
            "name" => isset($json['description']) ? $json['description'] : $json['extra']['code'],
354
            "version" => $json['version'],
355
        ];
356
    }
357
358
    /**
359
     * @param string $path
360
     */
361
    public function deleteFile($path)
362
    {
363
        $f = new Filesystem();
364
        $f->remove($path);
365
    }
366
367
    public function checkSamePlugin($code)
368
    {
369
        $repo = $this->pluginRepository->findOneBy(['code' => $code]);
370
        if ($repo) {
371
            throw new PluginException('plugin already installed.');
372
        }
373
    }
374
375
    public function calcPluginDir($name)
376
    {
377
        return $this->projectRoot.'/app/Plugin/'.$name;
378
    }
379
380
    /**
381
     * @param string $d
382
     *
383
     * @throws PluginException
384
     */
385
    public function createPluginDir($d)
386
    {
387
        $b = @mkdir($d);
388
        if (!$b) {
389
            throw new PluginException($php_errormsg);
390
        }
391
    }
392
393
    /**
394
     * @param $meta
395
     * @param int $source
396
     *
397
     * @return Plugin
398
     *
399
     * @throws PluginException
400
     * @throws \Doctrine\DBAL\ConnectionException
401
     */
402
    public function registerPlugin($meta, $source = 0)
403
    {
404
        $em = $this->entityManager;
405
        $em->getConnection()->beginTransaction();
406
        try {
407
            $p = new Plugin();
408
            // インストール直後はプラグインは有効にしない
409
            $p->setName($meta['name'])
410
                ->setEnabled(false)
411
                ->setVersion($meta['version'])
412
                ->setSource($source)
413
                ->setCode($meta['code'])
414
                // TODO 日付の自動設定
415
                ->setCreateDate(new \DateTime())
416
                ->setUpdateDate(new \DateTime());
417
418
            $em->persist($p);
419
            $em->flush();
420
421
            $this->callPluginManagerMethod($meta, 'install');
422
423
            $em->flush();
424
            $em->getConnection()->commit();
425
        } catch (\Exception $e) {
426
            $em->getConnection()->rollback();
427
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
428
        }
429
430
        return $p;
431
    }
432
433
    /**
434
     * @param $meta
435
     * @param string $method
436
     */
437
    public function callPluginManagerMethod($meta, $method)
438
    {
439
        $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
440
        if (class_exists($class)) {
441
            $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
442
            if (method_exists($installer, $method)) {
443
                // FIXME appを削除.
444
                $installer->$method($meta, $this->app, $this->container);
445
            }
446
        }
447
    }
448
449
    /**
450
     * @param Plugin $plugin
451
     * @param bool $force
452
     *
453
     * @return bool
454
     * @throws \Exception
455
     */
456
    public function uninstall(Plugin $plugin, $force = true)
457
    {
458
        $pluginDir = $this->calcPluginDir($plugin->getCode());
459
        $this->cacheUtil->clearCache();
460
        $config = $this->readConfig($pluginDir);
461
        $this->callPluginManagerMethod($config, 'disable');
462
        $this->callPluginManagerMethod($config, 'uninstall');
463
        $this->disable($plugin);
464
        $this->unregisterPlugin($plugin);
465
466
        // スキーマを更新する
467
        //FIXME: Update schema before no affect
468
        $this->schemaService->updateSchema([], $this->projectRoot.'/app/proxy/entity');
469
470
        // プラグインのネームスペースに含まれるEntityのテーブルを削除する
471
        $namespace = 'Plugin\\'.$plugin->getCode().'\\Entity';
472
        $this->schemaService->dropTable($namespace);
473
474
        if ($force) {
475
            $this->deleteFile($pluginDir);
476
            $this->removeAssets($plugin->getCode());
477
        }
478
479
        return true;
480
    }
481
482
    public function unregisterPlugin(Plugin $p)
483
    {
484
        try {
485
            $em = $this->entityManager;
486
            $em->remove($p);
487
            $em->flush();
488
        } catch (\Exception $e) {
489
            throw $e;
490
        }
491
    }
492
493
    public function disable(Plugin $plugin)
494
    {
495
        return $this->enable($plugin, false);
496
    }
497
498
    /**
499
     * Proxyを再生成します.
500
     *
501
     * @param Plugin $plugin プラグイン
502
     * @param boolean $temporary プラグインが無効状態でも一時的に生成するかどうか
503
     * @param string|null $outputDir 出力先
504
     *
505
     * @return array 生成されたファイルのパス
506
     */
507
    private function regenerateProxy(Plugin $plugin, $temporary, $outputDir = null)
508
    {
509
        if (is_null($outputDir)) {
510
            $outputDir = $this->projectRoot.'/app/proxy/entity';
511
        }
512
        @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...
513
514
        $enabledPluginCodes = array_map(
515
            function ($p) { return $p->getCode(); },
516
            $this->pluginRepository->findAllEnabled()
517
        );
518
519
        $excludes = [];
520
        if ($temporary || $plugin->isEnabled()) {
521
            $enabledPluginCodes[] = $plugin->getCode();
522
        } else {
523
            $index = array_search($plugin->getCode(), $enabledPluginCodes);
524
            if ($index >= 0) {
525
                array_splice($enabledPluginCodes, $index, 1);
526
                $excludes = [$this->projectRoot.'/app/Plugin/'.$plugin->getCode().'/Entity'];
527
            }
528
        }
529
530
        $enabledPluginEntityDirs = array_map(function ($code) {
531
            return $this->projectRoot."/app/Plugin/${code}/Entity";
532
        }, $enabledPluginCodes);
533
534
        return $this->entityProxyService->generate(
535
            array_merge([$this->projectRoot.'/app/Customize/Entity'], $enabledPluginEntityDirs),
536
            $excludes,
537
            $outputDir
538
        );
539
    }
540
541
    public function enable(Plugin $plugin, $enable = true)
542
    {
543
        $em = $this->entityManager;
544
        try {
545
            $pluginDir = $this->calcPluginDir($plugin->getCode());
546
            $config = $this->readConfig($pluginDir);
547
            $em->getConnection()->beginTransaction();
548
            $plugin->setEnabled($enable ? true : false);
549
            $em->persist($plugin);
550
551
            $this->callPluginManagerMethod($config, $enable ? 'enable' : 'disable');
552
553
            // Proxyだけ再生成してスキーマは更新しない
554
            $this->regenerateProxy($plugin, false);
555
556
            $em->flush();
557
            $em->getConnection()->commit();
558
        } catch (\Exception $e) {
559
            $em->getConnection()->rollback();
560
            throw $e;
561
        }
562
563
        return true;
564
    }
565
566
    /**
567
     * Update plugin
568
     *
569
     * @param Plugin $plugin
570
     * @param string $path
571
     *
572
     * @return bool
573
     *
574
     * @throws PluginException
575
     * @throws \Exception
576
     */
577
    public function update(Plugin $plugin, $path)
578
    {
579
        $pluginBaseDir = null;
580
        $tmp = null;
581
        try {
582
            $this->cacheUtil->clearCache();
583
            $tmp = $this->createTempDir();
584
585
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
586
            $this->checkPluginArchiveContent($tmp);
587
588
            $config = $this->readConfig($tmp);
589
590
            if ($plugin->getCode() != $config['code']) {
591
                throw new PluginException('new/old plugin code is different.');
592
            }
593
594
            $pluginBaseDir = $this->calcPluginDir($config['code']);
595
            $this->deleteFile($tmp); // テンポラリのファイルを削除
596
597
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
598
599
            // Check dependent plugin
600
            // Don't install ec-cube library
601
            $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY);
602
            if (!empty($dependents)) {
603
                $package = $this->parseToComposerCommand($dependents);
604
                $this->composerService->execRequire($package);
605
            }
606
607
            $this->updatePlugin($plugin, $config); // dbにプラグイン登録
608
        } catch (PluginException $e) {
609
            $this->deleteDirs([$tmp]);
610
            throw $e;
611
        } catch (\Exception $e) {
612
            // catch exception of composer
613
            $this->deleteDirs([$tmp]);
614
            throw $e;
615
        }
616
617
        return true;
618
    }
619
620
    /**
621
     * Update plugin
622
     *
623
     * @param Plugin $plugin
624
     * @param array  $meta     Config data
625
     *
626
     * @throws \Exception
627
     */
628
    public function updatePlugin(Plugin $plugin, $meta)
629
    {
630
        $em = $this->entityManager;
631
        try {
632
            $em->getConnection()->beginTransaction();
633
            $plugin->setVersion($meta['version'])
634
                ->setName($meta['name']);
635
636
            $em->persist($plugin);
637
            $this->callPluginManagerMethod($meta, 'update');
638
            $em->flush();
639
            $em->getConnection()->commit();
640
        } catch (\Exception $e) {
641
            $em->getConnection()->rollback();
642
            throw $e;
643
        }
644
    }
645
646
    /**
647
     * Do check dependency plugin
648
     *
649
     * @param array $plugins    get from api
650
     * @param array $plugin     format as plugin from api
651
     * @param array $dependents template output
652
     *
653
     * @return array|mixed
654
     */
655
    public function getDependency($plugins, $plugin, $dependents = [])
656
    {
657
        // Prevent infinity loop
658
        if (empty($dependents)) {
659
            $dependents[] = $plugin;
660
        }
661
662
        // Check dependency
663
        if (!isset($plugin['require']) || empty($plugin['require'])) {
664
            return $dependents;
665
        }
666
667
        $require = $plugin['require'];
668
        // Check dependency
669
        foreach ($require as $pluginName => $version) {
670
            $dependPlugin = $this->buildInfo($plugins, $pluginName);
671
            // Prevent call self
672
            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...
673
                continue;
674
            }
675
676
            // Check duplicate in dependency
677
            $index = array_search($dependPlugin['product_code'], array_column($dependents, 'product_code'));
678
            if ($index === false) {
679
                // Update require version
680
                $dependPlugin['version'] = $version;
681
                $dependents[] = $dependPlugin;
682
                // Check child dependency
683
                $dependents = $this->getDependency($plugins, $dependPlugin, $dependents);
684
            }
685
        }
686
687
        return $dependents;
688
    }
689
690
    /**
691
     * Get plugin information
692
     *
693
     * @param array  $plugins    get from api
694
     * @param string $pluginCode
695
     *
696
     * @return array|null
697
     */
698
    public function buildInfo($plugins, $pluginCode)
699
    {
700
        $plugin = [];
701
        $index = $this->checkPluginExist($plugins, $pluginCode);
702
        if ($index === false) {
703
            return $plugin;
704
        }
705
        // Get target plugin in return of api
706
        $plugin = $plugins[$index];
707
708
        // Check the eccube version that the plugin supports.
709
        $plugin['is_supported_eccube_version'] = 0;
710
        if (in_array(Constant::VERSION, $plugin['eccube_version'])) {
711
            // Match version
712
            $plugin['is_supported_eccube_version'] = 1;
713
        }
714
715
        $plugin['depend'] = $this->getRequirePluginName($plugins, $plugin);
716
717
        return $plugin;
718
    }
719
720
    /**
721
     * Get dependency name and version only
722
     *
723
     * @param array $plugins get from api
724
     * @param array $plugin  target plugin from api
725
     *
726
     * @return mixed format [0 => ['name' => pluginName1, 'version' => pluginVersion1], 1 => ['name' => pluginName2, 'version' => pluginVersion2]]
727
     */
728
    public function getRequirePluginName($plugins, $plugin)
729
    {
730
        $depend = [];
731
        if (isset($plugin['require']) && !empty($plugin['require'])) {
732
            foreach ($plugin['require'] as $name => $version) {
733
                $ret = $this->checkPluginExist($plugins, $name);
734
                if ($ret === false) {
735
                    continue;
736
                }
737
                $depend[] = [
738
                    'name' => $plugins[$ret]['name'],
739
                    'version' => $version,
740
                ];
741
            }
742
        }
743
744
        return $depend;
745
    }
746
747
    /**
748
     * Check require plugin in enable
749
     *
750
     * @param string $pluginCode
751
     *
752
     * @return array plugin code
753
     */
754
    public function findRequirePluginNeedEnable($pluginCode)
755
    {
756
        $dir = $this->eccubeConfig['plugin_realdir'].'/'.$pluginCode;
757
        $composerFile = $dir.'/composer.json';
758
        if (!file_exists($composerFile)) {
759
            return [];
760
        }
761
        $jsonText = file_get_contents($composerFile);
762
        $json = json_decode($jsonText, true);
763
        // Check require
764
        if (!isset($json['require']) || empty($json['require'])) {
765
            return [];
766
        }
767
        $require = $json['require'];
768
769
        // Remove vendor plugin
770
        if (isset($require[self::VENDOR_NAME.'/plugin-installer'])) {
771
            unset($require[self::VENDOR_NAME.'/plugin-installer']);
772
        }
773
        $requires = [];
774
        foreach ($require as $name => $version) {
775
            // Check plugin of ec-cube only
776
            if (strpos($name, self::VENDOR_NAME.'/') !== false) {
777
                $requireCode = str_replace(self::VENDOR_NAME.'/', '', $name);
778
                $ret = $this->isEnable($requireCode);
779
                if ($ret) {
780
                    continue;
781
                }
782
                $requires[] = $requireCode;
783
            }
784
        }
785
786
        return $requires;
787
    }
788
789
    /**
790
     * Find the dependent plugins that need to be disabled
791
     *
792
     * @param string $pluginCode
793
     *
794
     * @return array plugin code
795
     */
796
    public function findDependentPluginNeedDisable($pluginCode)
797
    {
798
        return $this->findDependentPlugin($pluginCode, true);
799
    }
800
801
    /**
802
     * Find the other plugin that has requires on it.
803
     * Check in both dtb_plugin table and <PluginCode>/composer.json
804
     *
805
     * @param string $pluginCode
806
     * @param bool   $enableOnly
807
     *
808
     * @return array plugin code
809
     */
810
    public function findDependentPlugin($pluginCode, $enableOnly = false)
811
    {
812
        $criteria = Criteria::create()
813
            ->where(Criteria::expr()->neq('code', $pluginCode));
814
        if ($enableOnly) {
815
            $criteria->andWhere(Criteria::expr()->eq('enabled', Constant::ENABLED));
816
        }
817
        /**
818
         * @var Plugin[]
819
         */
820
        $plugins = $this->pluginRepository->matching($criteria);
821
        $dependents = [];
822
        foreach ($plugins as $plugin) {
823
            $dir = $this->eccubeConfig['plugin_realdir'].'/'.$plugin->getCode();
824
            $fileName = $dir.'/composer.json';
825
            if (!file_exists($fileName)) {
826
                continue;
827
            }
828
            $jsonText = file_get_contents($fileName);
829
            if ($jsonText) {
830
                $json = json_decode($jsonText, true);
831
                if (!isset($json['require'])) {
832
                    continue;
833
                }
834
                if (array_key_exists(self::VENDOR_NAME.'/'.$pluginCode, $json['require'])) {
835
                    $dependents[] = $plugin->getCode();
836
                }
837
            }
838
        }
839
840
        return $dependents;
841
    }
842
843
    /**
844
     * Get dependent plugin by code
845
     * It's base on composer.json
846
     * Return the plugin code and version in the format of the composer
847
     *
848
     * @param string   $pluginCode
849
     * @param int|null $libraryType
850
     *                      self::ECCUBE_LIBRARY only return library/plugin of eccube
851
     *                      self::OTHER_LIBRARY only return library/plugin of 3rd part ex: symfony, composer, ...
852
     *                      default : return all library/plugin
853
     *
854
     * @return array format [packageName1 => version1, packageName2 => version2]
855
     */
856
    public function getDependentByCode($pluginCode, $libraryType = null)
857
    {
858
        $pluginDir = $this->calcPluginDir($pluginCode);
859
        $jsonFile = $pluginDir.'/composer.json';
860
        if (!file_exists($jsonFile)) {
861
            return [];
862
        }
863
        $jsonText = file_get_contents($jsonFile);
864
        $json = json_decode($jsonText, true);
865
        $dependents = [];
866
        if (isset($json['require'])) {
867
            $require = $json['require'];
868
            switch ($libraryType) {
869 View Code Duplication
                case self::ECCUBE_LIBRARY:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
870
                    $dependents = array_intersect_key($require, array_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i', array_keys($require))));
871
                    break;
872
873 View Code Duplication
                case self::OTHER_LIBRARY:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
874
                    $dependents = array_intersect_key($require, array_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i', array_keys($require), PREG_GREP_INVERT)));
875
                    break;
876
877
                default:
878
                    $dependents = $json['require'];
879
                    break;
880
            }
881
        }
882
883
        return $dependents;
884
    }
885
886
    /**
887
     * Format array dependent plugin to string
888
     * It is used for commands.
889
     *
890
     * @param array $packages   format [packageName1 => version1, packageName2 => version2]
891
     * @param bool  $getVersion
892
     *
893
     * @return string format if version=true: "packageName1:version1 packageName2:version2", if version=false: "packageName1 packageName2"
894
     */
895
    public function parseToComposerCommand(array $packages, $getVersion = true)
896
    {
897
        $result = array_keys($packages);
898
        if ($getVersion) {
899
            $result = array_map(function ($package, $version) {
900
                return $package.':'.$version;
901
            }, array_keys($packages), array_values($packages));
902
        }
903
904
        return implode(' ', $result);
905
    }
906
907
    /**
908
     * リソースファイル等をコピー
909
     * コピー元となるファイルの置き場所は固定であり、
910
     * [プラグインコード]/Resource/assets
911
     * 配下に置かれているファイルが所定の位置へコピーされる
912
     *
913
     * @param string $pluginBaseDir
914
     * @param $pluginCode
915
     */
916
    public function copyAssets($pluginBaseDir, $pluginCode)
917
    {
918
        $assetsDir = $pluginBaseDir.'/Resource/assets';
919
920
        // プラグインにリソースファイルがあれば所定の位置へコピー
921
        if (file_exists($assetsDir)) {
922
            $file = new Filesystem();
923
            $file->mirror($assetsDir, $this->eccubeConfig['plugin_html_realdir'].$pluginCode.'/assets');
924
        }
925
    }
926
927
    /**
928
     * コピーしたリソースファイル等を削除
929
     *
930
     * @param string $pluginCode
931
     */
932
    public function removeAssets($pluginCode)
933
    {
934
        $assetsDir = $this->projectRoot.'/app/Plugin/'.$pluginCode;
935
936
        // コピーされているリソースファイルがあれば削除
937
        if (file_exists($assetsDir)) {
938
            $file = new Filesystem();
939
            $file->remove($assetsDir);
940
        }
941
    }
942
943
    /**
944
     * Is update
945
     *
946
     * @param string $pluginVersion
947
     * @param string $remoteVersion
948
     *
949
     * @return boolean
950
     */
951
    public function isUpdate($pluginVersion, $remoteVersion)
952
    {
953
        return version_compare($pluginVersion, $remoteVersion, '<');
954
    }
955
956
    /**
957
     * Plugin is exist check
958
     *
959
     * @param array  $plugins    get from api
960
     * @param string $pluginCode
961
     *
962
     * @return false|int|string
963
     */
964
    public function checkPluginExist($plugins, $pluginCode)
965
    {
966
        if (strpos($pluginCode, self::VENDOR_NAME.'/') !== false) {
967
            $pluginCode = str_replace(self::VENDOR_NAME.'/', '', $pluginCode);
968
        }
969
        // Find plugin in array
970
        $index = array_search($pluginCode, array_column($plugins, 'product_code'));
971
972
        return $index;
973
    }
974
975
    /**
976
     * @param string $code
977
     *
978
     * @return bool
979
     */
980
    private function isEnable($code)
981
    {
982
        $Plugin = $this->pluginRepository->findOneBy([
983
            'enabled' => Constant::ENABLED,
984
            'code' => $code,
985
        ]);
986
        if ($Plugin) {
987
            return true;
988
        }
989
990
        return false;
991
    }
992
}
993