Completed
Push — sf/composer-install-update ( a1e834 )
by Kiyotaka
10:44
created

PluginService::unpackPluginArchive()   A

Complexity

Conditions 3
Paths 8

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 8
nop 2
dl 0
loc 17
rs 9.7
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
31
class PluginService
32
{
33
    /**
34
     * @var EccubeConfig
35
     */
36
    protected $eccubeConfig;
37
38
    /**
39
     * @var EntityManager
40
     */
41
    protected $entityManager;
42
43
    /**
44
     * @var PluginRepository
45
     */
46
    protected $pluginRepository;
47
48
    /**
49
     * @var Application
50
     */
51
    protected $app;
52
53
    /**
54
     * @var EntityProxyService
55
     */
56
    protected $entityProxyService;
57
58
    /**
59
     * @var SchemaService
60
     */
61
    protected $schemaService;
62
63
    /**
64
     * @var ComposerServiceInterface
65
     */
66
    protected $composerService;
67
68
    const VENDOR_NAME = 'ec-cube';
69
70
    /**
71
     * Plugin type/library of ec-cube
72
     */
73
    const ECCUBE_LIBRARY = 1;
74
75
    /**
76
     * Plugin type/library of other (except ec-cube)
77
     */
78
    const OTHER_LIBRARY = 2;
79
80
    /**
81
     * @var string %kernel.project_dir%
82
     */
83
    private $projectRoot;
84
85
    /**
86
     * @var string %kernel.environment%
87
     */
88
    private $environment;
89
90
    /**
91
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
92
     */
93
    protected $container;
94
95
    /** @var CacheUtil */
96
    protected $cacheUtil;
97
98
    /**
99
     * PluginService constructor.
100
     *
101
     * @param EntityManagerInterface $entityManager
102
     * @param PluginRepository $pluginRepository
103
     * @param EntityProxyService $entityProxyService
104
     * @param SchemaService $schemaService
105
     * @param EccubeConfig $eccubeConfig
106
     * @param ContainerInterface $container
107
     * @param CacheUtil $cacheUtil
108
     */
109
    public function __construct(
110
        EntityManagerInterface $entityManager,
111
        PluginRepository $pluginRepository,
112
        EntityProxyService $entityProxyService,
113
        SchemaService $schemaService,
114
        EccubeConfig $eccubeConfig,
115
        ContainerInterface $container,
116
        CacheUtil $cacheUtil
117
    ) {
118
        $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...
119
        $this->pluginRepository = $pluginRepository;
120
        $this->entityProxyService = $entityProxyService;
121
        $this->schemaService = $schemaService;
122
        $this->eccubeConfig = $eccubeConfig;
123
        $this->projectRoot = $eccubeConfig->get('kernel.project_dir');
124
        $this->environment = $eccubeConfig->get('kernel.environment');
125
        $this->container = $container;
126
        $this->cacheUtil = $cacheUtil;
127
    }
128
129
    /**
130
     * ファイル指定してのプラグインインストール
131
     *
132
     * @param string $path   path to tar.gz/zip plugin file
133
     * @param int    $source
134
     *
135
     * @return boolean
136
     *
137
     * @throws PluginException
138
     * @throws \Exception
139
     */
140
    public function install($path, $source = 0)
141
    {
142
        $pluginBaseDir = null;
143
        $tmp = null;
144
        try {
145
            // プラグイン配置前に実施する処理
146
            $this->preInstall();
147
            $tmp = $this->createTempDir();
148
149
            // 一旦テンポラリに展開
150
            $this->unpackPluginArchive($path, $tmp);
151
            $this->checkPluginArchiveContent($tmp);
152
153
            $config = $this->readConfig($tmp);
154
            // テンポラリのファイルを削除
155
            $this->deleteFile($tmp);
156
157
            // 重複していないかチェック
158
            $this->checkSamePlugin($config['code']);
159
160
            $pluginBaseDir = $this->calcPluginDir($config['code']);
161
            // 本来の置き場所を作成
162
            $this->createPluginDir($pluginBaseDir);
163
164
            // 問題なければ本当のplugindirへ
165
            $this->unpackPluginArchive($path, $pluginBaseDir);
166
167
            // Check dependent plugin
168
            // Don't install ec-cube library
169
//            $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY);
170
//            if (!empty($dependents)) {
171
//                $package = $this->parseToComposerCommand($dependents);
172
            //FIXME: how to working with ComposerProcessService or ComposerApiService ?
173
//                $this->composerService->execRequire($package);
174
//            }
175
176
            // プラグイン配置後に実施する処理
177
            $this->postInstall($config, $source);
178
            // リソースファイルをコピー
179
            $this->copyAssets($pluginBaseDir, $config['code']);
180
        } catch (PluginException $e) {
181
            $this->deleteDirs([$tmp, $pluginBaseDir]);
182
            throw $e;
183
        } catch (\Exception $e) {
184
            // インストーラがどんなExceptionを上げるかわからないので
185
            $this->deleteDirs([$tmp, $pluginBaseDir]);
186
            throw $e;
187
        }
188
189
        return true;
190
    }
191
192
    // インストール事前処理
193
    public function preInstall()
194
    {
195
        // キャッシュの削除
196
        // FIXME: Please fix clearCache function (because it's clear all cache and this file just upload)
197
//        $this->cacheUtil->clearCache();
198
    }
199
200
    // インストール事後処理
201
    public function postInstall($config, $source)
202
    {
203
        // Proxyのクラスをロードせずにスキーマを更新するために、
204
        // インストール時には一時的なディレクトリにProxyを生成する
205
        $tmpProxyOutputDir = sys_get_temp_dir().'/proxy_'.StringUtil::random(12);
206
        @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...
207
208
        try {
209
            // dbにプラグイン登録
210
            $plugin = $this->registerPlugin($config, $source);
211
212
            // プラグインmetadata定義を追加
213
            $entityDir = $this->eccubeConfig['plugin_realdir'].'/'.$plugin->getCode().'/Entity';
214
            if (file_exists($entityDir)) {
215
                $ormConfig = $this->entityManager->getConfiguration();
216
                $chain = $ormConfig->getMetadataDriverImpl();
217
                $driver = $ormConfig->newDefaultAnnotationDriver([$entityDir], false);
218
                $namespace = 'Plugin\\'.$config['code'].'\\Entity';
219
                $chain->addDriver($driver, $namespace);
220
                $ormConfig->addEntityNamespace($plugin->getCode(), $namespace);
221
            }
222
223
            // インストール時には一時的に利用するProxyを生成してからスキーマを更新する
224
            $generatedFiles = $this->regenerateProxy($plugin, true, $tmpProxyOutputDir);
225
            $this->schemaService->updateSchema($generatedFiles, $tmpProxyOutputDir);
226
        } finally {
227
            foreach (glob("${tmpProxyOutputDir}/*") as  $f) {
228
                unlink($f);
229
            }
230
            rmdir($tmpProxyOutputDir);
231
        }
232
    }
233
234
    public function createTempDir()
235
    {
236
        $tempDir = $this->projectRoot.'/var/cache/'.$this->environment.'/Plugin';
237
        @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...
238
        $d = ($tempDir.'/'.sha1(StringUtil::random(16)));
239
240
        if (!mkdir($d, 0777)) {
241
            throw new PluginException($php_errormsg.$d);
242
        }
243
244
        return $d;
245
    }
246
247
    public function deleteDirs($arr)
248
    {
249
        foreach ($arr as $dir) {
250
            if (file_exists($dir)) {
251
                $fs = new Filesystem();
252
                $fs->remove($dir);
253
            }
254
        }
255
    }
256
257
    /**
258
     * @param string $archive
259
     * @param string $dir
260
     *
261
     * @throws PluginException
262
     */
263
    public function unpackPluginArchive($archive, $dir)
264
    {
265
        $extension = pathinfo($archive, PATHINFO_EXTENSION);
266
        try {
267
            if ($extension == 'zip') {
268
                $zip = new \ZipArchive();
269
                $zip->open($archive);
270
                $zip->extractTo($dir);
271
                $zip->close();
272
            } else {
273
                $phar = new \PharData($archive);
274
                $phar->extractTo($dir, null, true);
275
            }
276
        } catch (\Exception $e) {
277
            throw new PluginException(trans('pluginservice.text.error.upload_failure'));
278
        }
279
    }
280
281
    /**
282
     * @param $dir
283
     * @param array $config_cache
284
     *
285
     * @throws PluginException
286
     */
287
    public function checkPluginArchiveContent($dir, array $config_cache = [])
288
    {
289
        try {
290
            if (!empty($config_cache)) {
291
                $meta = $config_cache;
292
            } else {
293
                $meta = $this->readConfig($dir);
294
            }
295
        } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
296
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
297
        }
298
299
        if (!is_array($meta)) {
300
            throw new PluginException('config.yml not found or syntax error');
301
        }
302
        if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
303
            throw new PluginException('config.yml code empty or invalid_character(\W)');
304
        }
305
        if (!isset($meta['name'])) {
306
            // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
307
            throw new PluginException('config.yml name empty');
308
        }
309
        if (!isset($meta['version'])) {
310
            // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
311
            throw new PluginException('config.yml version invalid_character(\W) ');
312
        }
313
        if (isset($meta['orm.path'])) {
314
            if (!is_array($meta['orm.path'])) {
315
                throw new PluginException('config.yml orm.path invalid_character(\W) ');
316
            }
317
        }
318
        if (isset($meta['service'])) {
319
            if (!is_array($meta['service'])) {
320
                throw new PluginException('config.yml service invalid_character(\W) ');
321
            }
322
        }
323
    }
324
325
    /**
326
     * @param $pluginDir
327
     *
328
     * @return array
329
     *
330
     * @throws PluginException
331
     */
332
    public function readConfig($pluginDir)
333
    {
334
        $composerJsonPath = $pluginDir.DIRECTORY_SEPARATOR.'composer.json';
335
        if (file_exists($composerJsonPath) === false) {
336
            throw new PluginException("${composerJsonPath} not found.");
337
        }
338
339
        $json = json_decode(file_get_contents($composerJsonPath), true);
340
        if ($json === null) {
341
            throw new PluginException("Invalid json format. [${composerJsonPath}]");
342
        }
343
344
        if (!isset($json['version'])) {
345
            throw new PluginException("`version` is not defined in ${composerJsonPath}");
346
        }
347
348
        if (!isset($json['extra']['code'])) {
349
            throw new PluginException("`extra.code` is not defined in ${composerJsonPath}");
350
        }
351
352
        return [
353
            'code' => $json['extra']['code'],
354
            'name' => isset($json['description']) ? $json['description'] : $json['extra']['code'],
355
            'version' => $json['version'],
356
        ];
357
    }
358
359
    public function checkSymbolName($string)
360
    {
361
        return strlen($string) < 256 && preg_match('/^\w+$/', $string);
362
        // plugin_nameやplugin_codeに使える文字のチェック
363
        // a-z A-Z 0-9 _
364
        // ディレクトリ名などに使われれるので厳しめ
365
    }
366
367
    /**
368
     * @param string $path
369
     */
370
    public function deleteFile($path)
371
    {
372
        $f = new Filesystem();
373
        $f->remove($path);
374
    }
375
376
    public function checkSamePlugin($code)
377
    {
378
        $repo = $this->pluginRepository->findOneBy(['code' => $code]);
379
        if ($repo) {
380
            throw new PluginException('plugin already installed.');
381
        }
382
    }
383
384
    public function calcPluginDir($name)
385
    {
386
        return $this->projectRoot.'/app/Plugin/'.$name;
387
    }
388
389
    /**
390
     * @param string $d
391
     *
392
     * @throws PluginException
393
     */
394
    public function createPluginDir($d)
395
    {
396
        $b = @mkdir($d);
397
        if (!$b) {
398
            throw new PluginException($php_errormsg);
399
        }
400
    }
401
402
    /**
403
     * @param $meta
404
     * @param int $source
405
     *
406
     * @return Plugin
407
     *
408
     * @throws PluginException
409
     * @throws \Doctrine\DBAL\ConnectionException
410
     */
411
    public function registerPlugin($meta, $source = 0)
412
    {
413
        $em = $this->entityManager;
414
        $em->getConnection()->beginTransaction();
415
        try {
416
            $p = new Plugin();
417
            // インストール直後はプラグインは有効にしない
418
            $p->setName($meta['name'])
419
                ->setEnabled(false)
420
                ->setVersion($meta['version'])
421
                ->setSource($source)
422
                ->setCode($meta['code'])
423
                // TODO 日付の自動設定
424
                ->setCreateDate(new \DateTime())
425
                ->setUpdateDate(new \DateTime());
426
427
            $em->persist($p);
428
            $em->flush();
429
430
            $this->callPluginManagerMethod($meta, 'install');
431
432
            $em->flush();
433
            $em->getConnection()->commit();
434
        } catch (\Exception $e) {
435
            $em->getConnection()->rollback();
436
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
437
        }
438
439
        return $p;
440
    }
441
442
    /**
443
     * @param $meta
444
     * @param string $method
445
     */
446
    public function callPluginManagerMethod($meta, $method)
447
    {
448
        $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
449
        if (class_exists($class)) {
450
            $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
451
            if (method_exists($installer, $method)) {
452
                // FIXME appを削除.
453
                $installer->$method($meta, $this->app, $this->container);
454
            }
455
        }
456
    }
457
458
    /**
459
     * @param Plugin $plugin
460
     * @param bool $force
461
     *
462
     * @return bool
463
     *
464
     * @throws \Exception
465
     */
466
    public function uninstall(Plugin $plugin, $force = true)
467
    {
468
        $pluginDir = $this->calcPluginDir($plugin->getCode());
469
        $this->cacheUtil->clearCache();
470
        $config = $this->readConfig($pluginDir);
471
        $this->callPluginManagerMethod($config, 'disable');
472
        $this->callPluginManagerMethod($config, 'uninstall');
473
        $this->disable($plugin);
474
        $this->unregisterPlugin($plugin);
475
476
        // スキーマを更新する
477
        //FIXME: Update schema before no affect
478
        $this->schemaService->updateSchema([], $this->projectRoot.'/app/proxy/entity');
479
480
        // プラグインのネームスペースに含まれるEntityのテーブルを削除する
481
        $namespace = 'Plugin\\'.$plugin->getCode().'\\Entity';
482
        $this->schemaService->dropTable($namespace);
483
484
        if ($force) {
485
            $this->deleteFile($pluginDir);
486
            $this->removeAssets($plugin->getCode());
487
        }
488
489
        return true;
490
    }
491
492
    public function unregisterPlugin(Plugin $p)
493
    {
494
        try {
495
            $em = $this->entityManager;
496
            $em->remove($p);
497
            $em->flush();
498
        } catch (\Exception $e) {
499
            throw $e;
500
        }
501
    }
502
503
    public function disable(Plugin $plugin)
504
    {
505
        return $this->enable($plugin, false);
506
    }
507
508
    /**
509
     * Proxyを再生成します.
510
     *
511
     * @param Plugin $plugin プラグイン
512
     * @param boolean $temporary プラグインが無効状態でも一時的に生成するかどうか
513
     * @param string|null $outputDir 出力先
514
     *
515
     * @return array 生成されたファイルのパス
516
     */
517
    private function regenerateProxy(Plugin $plugin, $temporary, $outputDir = null)
518
    {
519
        if (is_null($outputDir)) {
520
            $outputDir = $this->projectRoot.'/app/proxy/entity';
521
        }
522
        @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...
523
524
        $enabledPluginCodes = array_map(
525
            function ($p) { return $p->getCode(); },
526
            $this->pluginRepository->findAllEnabled()
527
        );
528
529
        $excludes = [];
530
        if ($temporary || $plugin->isEnabled()) {
531
            $enabledPluginCodes[] = $plugin->getCode();
532
        } else {
533
            $index = array_search($plugin->getCode(), $enabledPluginCodes);
534
            if ($index >= 0) {
535
                array_splice($enabledPluginCodes, $index, 1);
536
                $excludes = [$this->projectRoot.'/app/Plugin/'.$plugin->getCode().'/Entity'];
537
            }
538
        }
539
540
        $enabledPluginEntityDirs = array_map(function ($code) {
541
            return $this->projectRoot."/app/Plugin/${code}/Entity";
542
        }, $enabledPluginCodes);
543
544
        return $this->entityProxyService->generate(
545
            array_merge([$this->projectRoot.'/app/Customize/Entity'], $enabledPluginEntityDirs),
546
            $excludes,
547
            $outputDir
548
        );
549
    }
550
551
    public function enable(Plugin $plugin, $enable = true)
552
    {
553
        $em = $this->entityManager;
554
        try {
555
            $pluginDir = $this->calcPluginDir($plugin->getCode());
556
            $config = $this->readConfig($pluginDir);
557
            $em->getConnection()->beginTransaction();
558
            $plugin->setEnabled($enable ? true : false);
559
            $em->persist($plugin);
560
561
            $this->callPluginManagerMethod($config, $enable ? 'enable' : 'disable');
562
563
            // Proxyだけ再生成してスキーマは更新しない
564
            $this->regenerateProxy($plugin, false);
565
566
            $em->flush();
567
            $em->getConnection()->commit();
568
        } catch (\Exception $e) {
569
            $em->getConnection()->rollback();
570
            throw $e;
571
        }
572
573
        return true;
574
    }
575
576
    /**
577
     * Update plugin
578
     *
579
     * @param Plugin $plugin
580
     * @param string $path
581
     *
582
     * @return bool
583
     *
584
     * @throws PluginException
585
     * @throws \Exception
586
     */
587
    public function update(Plugin $plugin, $path)
588
    {
589
        $pluginBaseDir = null;
590
        $tmp = null;
591
        try {
592
            $this->cacheUtil->clearCache();
593
            $tmp = $this->createTempDir();
594
595
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
596
            $this->checkPluginArchiveContent($tmp);
597
598
            $config = $this->readConfig($tmp);
599
600
            if ($plugin->getCode() != $config['code']) {
601
                throw new PluginException('new/old plugin code is different.');
602
            }
603
604
            $pluginBaseDir = $this->calcPluginDir($config['code']);
605
            $this->deleteFile($tmp); // テンポラリのファイルを削除
606
607
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
608
609
            // Check dependent plugin
610
            // Don't install ec-cube library
611
            $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY);
612
            if (!empty($dependents)) {
613
                $package = $this->parseToComposerCommand($dependents);
614
                $this->composerService->execRequire($package);
615
            }
616
617
            $this->updatePlugin($plugin, $config); // dbにプラグイン登録
618
        } catch (PluginException $e) {
619
            $this->deleteDirs([$tmp]);
620
            throw $e;
621
        } catch (\Exception $e) {
622
            // catch exception of composer
623
            $this->deleteDirs([$tmp]);
624
            throw $e;
625
        }
626
627
        return true;
628
    }
629
630
    /**
631
     * Update plugin
632
     *
633
     * @param Plugin $plugin
634
     * @param array  $meta     Config data
635
     *
636
     * @throws \Exception
637
     */
638
    public function updatePlugin(Plugin $plugin, $meta)
639
    {
640
        $em = $this->entityManager;
641
        try {
642
            $em->getConnection()->beginTransaction();
643
            $plugin->setVersion($meta['version'])
644
                ->setName($meta['name']);
645
646
            $em->persist($plugin);
647
            $this->callPluginManagerMethod($meta, 'update');
648
            $em->flush();
649
            $em->getConnection()->commit();
650
        } catch (\Exception $e) {
651
            $em->getConnection()->rollback();
652
            throw $e;
653
        }
654
    }
655
656
    /**
657
     * Get array require by plugin
658
     * Todo: need define dependency plugin mechanism
659
     *
660
     * @param array $plugin     format as plugin from api
661
     *
662
     * @return array|mixed
663
     */
664
    public function getPluginRequired($plugin)
665
    {
666
        // Check require
667
        if (!isset($plugin['require']) || empty($plugin['require'])) {
668
            return [];
669
        }
670
//        $require = $plugin['require'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
671
672
        $requirePlugins = [];
673
        // Check require
0 ignored issues
show
Unused Code Comprehensibility introduced by
51% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
674
//        foreach ($require as $pluginName => $version) {
675
//            $pluginCode = str_replace(self::VENDOR_NAME . '/', '', $pluginName);
676
//            $pluginCode = Container::camelize($pluginCode);
677
//            $dependPlugin = $this->buildInfo($plugins, $pluginName);
678
//            // Prevent call self
679
//            if (!$dependPlugin || $dependPlugin['product_code'] == $plugin['product_code']) {
680
//                continue;
681
//            }
682
//
683
//            // Check duplicate in dependency
684
//            $index = array_search($dependPlugin['product_code'], array_column($dependents, 'product_code'));
685
//            if ($index === false) {
686
//                // Update require version
687
//                $dependPlugin['version'] = $version;
688
//                $dependents[] = $dependPlugin;
689
//                // Check child dependency
690
//                $dependents = $this->getPluginRequired($plugins, $dependPlugin, $dependents);
691
//            }
692
//        }
693
694
        return $requirePlugins;
695
    }
696
697
    /**
698
     * Get plugin information
699
     *
700
     * @param array $plugin
701
     *
702
     * @return array|null
703
     */
704
    public function buildInfo($plugin)
705
    {
706
        $this->supportedVersion($plugin);
707
        $plugin['require'] = $this->getRequireOfPlugin($plugin);
708
709
        return $plugin;
710
    }
711
712
    /**
713
     * Check support version
714
     *
715
     * @param $plugin
716
     */
717
    public function supportedVersion(&$plugin)
718
    {
719
        // Check the eccube version that the plugin supports.
720
        $plugin['version_check'] = false;
721
        if (in_array(Constant::VERSION, $plugin['supported_versions'])) {
722
            // Match version
723
            $plugin['version_check'] = true;
724
        }
725
    }
726
727
    /**
728
     * Get require plugin
729
     *
730
     * @param array $plugin  target plugin from api
731
     *
732
     * @return mixed format [0 => ['name' => pluginName1, 'version' => pluginVersion1], 1 => ['name' => pluginName2, 'version' => pluginVersion2]]
733
     */
734
    public function getRequireOfPlugin($plugin)
0 ignored issues
show
Unused Code introduced by
The parameter $plugin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
735
    {
736
//        $pluginCode = $plugin['code'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
737
        // Need dependency Mechanism
738
        /**
739
        $pluginDir = $this->calcPluginDir($pluginCode);
740
        $composerPath = $pluginDir.'/composer.json';
741
        // read composer.json
742
        if (!file_exists($composerPath)) {
743
            return [];
744
        }
745
        $content = file_get_contents($composerPath);
746
        $content = json_decode($content,true);
747
748
        $require = [];
749
        if (isset($content['require']) && !empty($content['require'])) {
750
            foreach ($content['require'] as $name => $version) {
751
                $require[] = [
752
                    'name' => $name,
753
                    'version' => $version,
754
                ];
755
            }
756
        }
757
         */
758
759
        return [];
760
    }
761
762
    /**
763
     * Check require plugin in enable
764
     *
765
     * @param string $pluginCode
766
     *
767
     * @return array plugin code
768
     */
769
    public function findRequirePluginNeedEnable($pluginCode)
770
    {
771
        $dir = $this->eccubeConfig['plugin_realdir'].'/'.$pluginCode;
772
        $composerFile = $dir.'/composer.json';
773
        if (!file_exists($composerFile)) {
774
            return [];
775
        }
776
        $jsonText = file_get_contents($composerFile);
777
        $json = json_decode($jsonText, true);
778
        // Check require
779
        if (!isset($json['require']) || empty($json['require'])) {
780
            return [];
781
        }
782
        $require = $json['require'];
783
784
        // Remove vendor plugin
785
        if (isset($require[self::VENDOR_NAME.'/plugin-installer'])) {
786
            unset($require[self::VENDOR_NAME.'/plugin-installer']);
787
        }
788
        $requires = [];
789
        foreach ($require as $name => $version) {
790
            // Check plugin of ec-cube only
791
            if (strpos($name, self::VENDOR_NAME.'/') !== false) {
792
                $requireCode = str_replace(self::VENDOR_NAME.'/', '', $name);
793
                $ret = $this->isEnable($requireCode);
794
                if ($ret) {
795
                    continue;
796
                }
797
                $requires[] = $requireCode;
798
            }
799
        }
800
801
        return $requires;
802
    }
803
804
    /**
805
     * Find the dependent plugins that need to be disabled
806
     *
807
     * @param string $pluginCode
808
     *
809
     * @return array plugin code
810
     */
811
    public function findDependentPluginNeedDisable($pluginCode)
812
    {
813
        return $this->findDependentPlugin($pluginCode, true);
814
    }
815
816
    /**
817
     * Find the other plugin that has requires on it.
818
     * Check in both dtb_plugin table and <PluginCode>/composer.json
819
     *
820
     * @param string $pluginCode
821
     * @param bool   $enableOnly
822
     *
823
     * @return array plugin code
824
     */
825
    public function findDependentPlugin($pluginCode, $enableOnly = false)
826
    {
827
        $criteria = Criteria::create()
828
            ->where(Criteria::expr()->neq('code', $pluginCode));
829
        if ($enableOnly) {
830
            $criteria->andWhere(Criteria::expr()->eq('enabled', Constant::ENABLED));
831
        }
832
        /**
833
         * @var Plugin[]
834
         */
835
        $plugins = $this->pluginRepository->matching($criteria);
836
        $dependents = [];
837
        foreach ($plugins as $plugin) {
838
            $dir = $this->eccubeConfig['plugin_realdir'].'/'.$plugin->getCode();
839
            $fileName = $dir.'/composer.json';
840
            if (!file_exists($fileName)) {
841
                continue;
842
            }
843
            $jsonText = file_get_contents($fileName);
844
            if ($jsonText) {
845
                $json = json_decode($jsonText, true);
846
                if (!isset($json['require'])) {
847
                    continue;
848
                }
849
                if (array_key_exists(self::VENDOR_NAME.'/'.$pluginCode, $json['require'])) {
850
                    $dependents[] = $plugin->getCode();
851
                }
852
            }
853
        }
854
855
        return $dependents;
856
    }
857
858
    /**
859
     * Get dependent plugin by code
860
     * It's base on composer.json
861
     * Return the plugin code and version in the format of the composer
862
     *
863
     * @param string   $pluginCode
864
     * @param int|null $libraryType
865
     *                      self::ECCUBE_LIBRARY only return library/plugin of eccube
866
     *                      self::OTHER_LIBRARY only return library/plugin of 3rd part ex: symfony, composer, ...
867
     *                      default : return all library/plugin
868
     *
869
     * @return array format [packageName1 => version1, packageName2 => version2]
870
     */
871
    public function getDependentByCode($pluginCode, $libraryType = null)
872
    {
873
        $pluginDir = $this->calcPluginDir($pluginCode);
874
        $jsonFile = $pluginDir.'/composer.json';
875
        if (!file_exists($jsonFile)) {
876
            return [];
877
        }
878
        $jsonText = file_get_contents($jsonFile);
879
        $json = json_decode($jsonText, true);
880
        $dependents = [];
881
        if (isset($json['require'])) {
882
            $require = $json['require'];
883
            switch ($libraryType) {
884 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...
885
                    $dependents = array_intersect_key($require, array_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i', array_keys($require))));
886
                    break;
887
888 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...
889
                    $dependents = array_intersect_key($require, array_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i', array_keys($require), PREG_GREP_INVERT)));
890
                    break;
891
892
                default:
893
                    $dependents = $json['require'];
894
                    break;
895
            }
896
        }
897
898
        return $dependents;
899
    }
900
901
    /**
902
     * Format array dependent plugin to string
903
     * It is used for commands.
904
     *
905
     * @param array $packages   format [packageName1 => version1, packageName2 => version2]
906
     * @param bool  $getVersion
907
     *
908
     * @return string format if version=true: "packageName1:version1 packageName2:version2", if version=false: "packageName1 packageName2"
909
     */
910
    public function parseToComposerCommand(array $packages, $getVersion = true)
911
    {
912
        $result = array_keys($packages);
913
        if ($getVersion) {
914
            $result = array_map(function ($package, $version) {
915
                return $package.':'.$version;
916
            }, array_keys($packages), array_values($packages));
917
        }
918
919
        return implode(' ', $result);
920
    }
921
922
    /**
923
     * リソースファイル等をコピー
924
     * コピー元となるファイルの置き場所は固定であり、
925
     * [プラグインコード]/Resource/assets
926
     * 配下に置かれているファイルが所定の位置へコピーされる
927
     *
928
     * @param string $pluginBaseDir
929
     * @param $pluginCode
930
     */
931
    public function copyAssets($pluginBaseDir, $pluginCode)
932
    {
933
        $assetsDir = $pluginBaseDir.'/Resource/assets';
934
935
        // プラグインにリソースファイルがあれば所定の位置へコピー
936
        if (file_exists($assetsDir)) {
937
            $file = new Filesystem();
938
            $file->mirror($assetsDir, $this->eccubeConfig['plugin_html_realdir'].$pluginCode.'/assets');
939
        }
940
    }
941
942
    /**
943
     * コピーしたリソースファイル等を削除
944
     *
945
     * @param string $pluginCode
946
     */
947
    public function removeAssets($pluginCode)
948
    {
949
        $assetsDir = $this->projectRoot.'/app/Plugin/'.$pluginCode;
950
951
        // コピーされているリソースファイルがあれば削除
952
        if (file_exists($assetsDir)) {
953
            $file = new Filesystem();
954
            $file->remove($assetsDir);
955
        }
956
    }
957
958
    /**
959
     * Is update
960
     *
961
     * @param string $pluginVersion
962
     * @param string $remoteVersion
963
     *
964
     * @return boolean
965
     */
966
    public function isUpdate($pluginVersion, $remoteVersion)
967
    {
968
        return version_compare($pluginVersion, $remoteVersion, '<');
969
    }
970
971
    /**
972
     * Plugin is exist check
973
     *
974
     * @param array  $plugins    get from api
975
     * @param string $pluginCode
976
     *
977
     * @return false|int|string
978
     */
979
    public function checkPluginExist($plugins, $pluginCode)
980
    {
981
        if (strpos($pluginCode, self::VENDOR_NAME.'/') !== false) {
982
            $pluginCode = str_replace(self::VENDOR_NAME.'/', '', $pluginCode);
983
        }
984
        // Find plugin in array
985
        $index = array_search($pluginCode, array_column($plugins, 'product_code'));
986
987
        return $index;
988
    }
989
990
    /**
991
     * @param string $code
992
     *
993
     * @return bool
994
     */
995
    private function isEnable($code)
996
    {
997
        $Plugin = $this->pluginRepository->findOneBy([
998
            'enabled' => Constant::ENABLED,
999
            'code' => $code,
1000
        ]);
1001
        if ($Plugin) {
1002
            return true;
1003
        }
1004
1005
        return false;
1006
    }
1007
}
1008