src/Eccube/Service/PluginService.php line 470

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of EC-CUBE
  4.  *
  5.  * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6.  *
  7.  * http://www.ec-cube.co.jp/
  8.  *
  9.  * For the full copyright and license information, please view the LICENSE
  10.  * file that was distributed with this source code.
  11.  */
  12. namespace Eccube\Service;
  13. use Doctrine\Common\Collections\Criteria;
  14. use Doctrine\ORM\EntityManager;
  15. use Doctrine\ORM\EntityManagerInterface;
  16. use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
  17. use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException;
  18. use Eccube\Common\Constant;
  19. use Eccube\Common\EccubeConfig;
  20. use Eccube\Entity\Plugin;
  21. use Eccube\Exception\PluginException;
  22. use Eccube\Repository\PluginRepository;
  23. use Eccube\Service\Composer\ComposerServiceInterface;
  24. use Eccube\Util\CacheUtil;
  25. use Eccube\Util\StringUtil;
  26. use Symfony\Component\DependencyInjection\ContainerInterface;
  27. use Symfony\Component\Filesystem\Filesystem;
  28. use Symfony\Component\Finder\Finder;
  29. class PluginService
  30. {
  31.     /**
  32.      * @var EccubeConfig
  33.      */
  34.     protected $eccubeConfig;
  35.     /**
  36.      * @var EntityManager
  37.      */
  38.     protected $entityManager;
  39.     /**
  40.      * @var PluginRepository
  41.      */
  42.     protected $pluginRepository;
  43.     /**
  44.      * @var EntityProxyService
  45.      */
  46.     protected $entityProxyService;
  47.     /**
  48.      * @var SchemaService
  49.      */
  50.     protected $schemaService;
  51.     /**
  52.      * @var ComposerServiceInterface
  53.      */
  54.     protected $composerService;
  55.     public const VENDOR_NAME 'ec-cube';
  56.     /**
  57.      * Plugin type/library of ec-cube
  58.      */
  59.     public const ECCUBE_LIBRARY 1;
  60.     /**
  61.      * Plugin type/library of other (except ec-cube)
  62.      */
  63.     public const OTHER_LIBRARY 2;
  64.     /**
  65.      * @var string %kernel.project_dir%
  66.      */
  67.     private $projectRoot;
  68.     /**
  69.      * @var string %kernel.environment%
  70.      */
  71.     private $environment;
  72.     /**
  73.      * @var ContainerInterface
  74.      */
  75.     protected $container;
  76.     /** @var CacheUtil */
  77.     protected $cacheUtil;
  78.     /**
  79.      * @var PluginApiService
  80.      */
  81.     private $pluginApiService;
  82.     /**
  83.      * @var SystemService
  84.      */
  85.     private $systemService;
  86.     /**
  87.      * @var PluginContext
  88.      */
  89.     private $pluginContext;
  90.     /**
  91.      * PluginService constructor.
  92.      *
  93.      * @param EntityManagerInterface $entityManager
  94.      * @param PluginRepository $pluginRepository
  95.      * @param EntityProxyService $entityProxyService
  96.      * @param SchemaService $schemaService
  97.      * @param EccubeConfig $eccubeConfig
  98.      * @param ContainerInterface $container
  99.      * @param CacheUtil $cacheUtil
  100.      * @param ComposerServiceInterface $composerService
  101.      * @param PluginApiService $pluginApiService
  102.      * @param SystemService $systemService
  103.      * @param PluginContext $pluginContext
  104.      */
  105.     public function __construct(
  106.         EntityManagerInterface $entityManager,
  107.         PluginRepository $pluginRepository,
  108.         EntityProxyService $entityProxyService,
  109.         SchemaService $schemaService,
  110.         EccubeConfig $eccubeConfig,
  111.         ContainerInterface $container,
  112.         CacheUtil $cacheUtil,
  113.         ComposerServiceInterface $composerService,
  114.         PluginApiService $pluginApiService,
  115.         SystemService $systemService,
  116.         PluginContext $pluginContext
  117.     ) {
  118.         $this->entityManager $entityManager;
  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.         $this->composerService $composerService;
  128.         $this->pluginApiService $pluginApiService;
  129.         $this->systemService $systemService;
  130.         $this->pluginContext $pluginContext;
  131.     }
  132.     /**
  133.      * ファイル指定してのプラグインインストール
  134.      *
  135.      * @param string $path   path to tar.gz/zip plugin file
  136.      * @param int    $source
  137.      *
  138.      * @return boolean
  139.      *
  140.      * @throws PluginException
  141.      * @throws \Exception
  142.      */
  143.     public function install($path$source 0)
  144.     {
  145.         $pluginBaseDir null;
  146.         $tmp null;
  147.         try {
  148.             // プラグイン配置前に実施する処理
  149.             $this->preInstall();
  150.             $tmp $this->createTempDir();
  151.             // 一旦テンポラリに展開
  152.             $this->unpackPluginArchive($path$tmp);
  153.             $this->checkPluginArchiveContent($tmp);
  154.             $config $this->readConfig($tmp);
  155.             // テンポラリのファイルを削除
  156.             $this->deleteFile($tmp);
  157.             // 重複していないかチェック
  158.             $this->checkSamePlugin($config['code']);
  159.             $pluginBaseDir $this->calcPluginDir($config['code']);
  160.             // 本来の置き場所を作成
  161.             $this->createPluginDir($pluginBaseDir);
  162.             // 問題なければ本当のplugindirへ
  163.             $this->unpackPluginArchive($path$pluginBaseDir);
  164.             // リソースファイルをコピー
  165.             $this->copyAssets($config['code']);
  166.             // プラグイン配置後に実施する処理
  167.             $this->postInstall($config$source);
  168.         } catch (PluginException $e) {
  169.             $this->deleteDirs([$tmp$pluginBaseDir]);
  170.             throw $e;
  171.         } catch (\Exception $e) {
  172.             // インストーラがどんなExceptionを上げるかわからないので
  173.             $this->deleteDirs([$tmp$pluginBaseDir]);
  174.             throw $e;
  175.         }
  176.         return true;
  177.     }
  178.     /**
  179.      * @param $code string sプラグインコード
  180.      *
  181.      * @throws PluginException
  182.      */
  183.     public function installWithCode($code)
  184.     {
  185.         $this->pluginContext->setCode($code);
  186.         $this->pluginContext->setInstall();
  187.         $pluginDir $this->calcPluginDir($code);
  188.         $this->checkPluginArchiveContent($pluginDir);
  189.         $config $this->readConfig($pluginDir);
  190.         if (isset($config['source']) && $config['source']) {
  191.             // 依存プラグインが有効になっていない場合はエラー
  192.             $requires $this->getPluginRequired($config);
  193.             $notInstalledOrDisabled array_filter($requires, function ($req) {
  194.                 $code preg_replace('/^ec-cube\//i'''$req['name']);
  195.                 /** @var Plugin $DependPlugin */
  196.                 $DependPlugin $this->pluginRepository->findByCode($code);
  197.                 return $DependPlugin $DependPlugin->isEnabled() == false true;
  198.             });
  199.             if (!empty($notInstalledOrDisabled)) {
  200.                 $names array_map(function ($p) { return $p['name']; }, $notInstalledOrDisabled);
  201.                 throw new PluginException(implode(', '$names).'を有効化してください。');
  202.             }
  203.         }
  204.         $this->checkSamePlugin($config['code']);
  205.         $this->copyAssets($config['code']);
  206.         $this->postInstall($config$config['source']);
  207.     }
  208.     // インストール事前処理
  209.     public function preInstall()
  210.     {
  211.         // キャッシュの削除
  212.         // FIXME: Please fix clearCache function (because it's clear all cache and this file just upload)
  213. //        $this->cacheUtil->clearCache();
  214.     }
  215.     // インストール事後処理
  216.     public function postInstall($config$source)
  217.     {
  218.         // dbにプラグイン登録
  219.         $this->entityManager->getConnection()->beginTransaction();
  220.         try {
  221.             $Plugin $this->pluginRepository->findByCode($config['code']);
  222.             if (!$Plugin) {
  223.                 $Plugin = new Plugin();
  224.                 // インストール直後はプラグインは有効にしない
  225.                 $Plugin->setName($config['name'])
  226.                     ->setEnabled(false)
  227.                     ->setVersion($config['version'])
  228.                     ->setSource($source)
  229.                     ->setCode($config['code']);
  230.                 $this->entityManager->persist($Plugin);
  231.                 $this->entityManager->flush();
  232.             }
  233.             $this->generateProxyAndUpdateSchema($Plugin$config);
  234.             $this->callPluginManagerMethod($config'install');
  235.             $Plugin->setInitialized(true);
  236.             $this->entityManager->persist($Plugin);
  237.             $this->entityManager->flush();
  238.             if ($this->entityManager->getConnection()->getNativeConnection()->inTransaction()) {
  239.                 $this->entityManager->getConnection()->commit();
  240.             }
  241.         } catch (\Exception $e) {
  242.             if ($this->entityManager->getConnection()->getNativeConnection()->inTransaction()) {
  243.                 if ($this->entityManager->getConnection()->isRollbackOnly()) {
  244.                     $this->entityManager->getConnection()->rollback();
  245.                 }
  246.             }
  247.             throw new PluginException($e->getMessage(), $e->getCode(), $e);
  248.         }
  249.     }
  250.     /**
  251.      * プラグインの Proxy ファイルを生成して UpdateSchema を実行する.
  252.      *
  253.      * @param Plugin $plugin プラグインオブジェクト
  254.      * @param array $config プラグインの composer.json の配列
  255.      * @param bool $uninstall アンインストールする場合は true
  256.      * @param bool $saveMode SQL を即時実行する場合は true
  257.      */
  258.     public function generateProxyAndUpdateSchema(Plugin $plugin$config$uninstall false$saveMode true)
  259.     {
  260.         // キャッシュしたメタデータを利用しないようにキャッシュドライバを外しておく
  261.         $this->entityManager->getMetadataFactory()->setCacheDriver(null);
  262.         $this->generateProxyAndCallback(function ($generatedFiles$proxiesDirectory) use ($saveMode) {
  263.             $this->schemaService->updateSchema($generatedFiles$proxiesDirectory$saveMode);
  264.         }, $plugin$config$uninstall);
  265.     }
  266.     /**
  267.      * プラグインの Proxy ファイルを生成してコールバック関数を実行する.
  268.      *
  269.      * コールバック関数は主に SchemaTool が利用されます.
  270.      * Proxy ファイルを出力する一時ディレクトリを指定しない場合は内部で生成し, コールバック関数実行後に削除されます.
  271.      *
  272.      * @param callable $callback Proxy ファイルを生成した後に実行されるコールバック関数
  273.      * @param Plugin $plugin プラグインオブジェクト
  274.      * @param array $config プラグインの composer.json の配列
  275.      * @param bool $uninstall アンインストールする場合は true
  276.      * @param string $tmpProxyOutputDir Proxy ファイルを出力する一時ディレクトリ
  277.      */
  278.     public function generateProxyAndCallback(callable $callbackPlugin $plugin$config$uninstall false$tmpProxyOutputDir null)
  279.     {
  280.         if ($plugin->isEnabled()) {
  281.             $generatedFiles $this->regenerateProxy($pluginfalse$tmpProxyOutputDir $tmpProxyOutputDir $this->projectRoot.'/app/proxy/entity');
  282.             call_user_func($callback$generatedFiles$tmpProxyOutputDir $tmpProxyOutputDir $this->projectRoot.'/app/proxy/entity');
  283.         } else {
  284.             // Proxyのクラスをロードせずにスキーマを更新するために、
  285.             // インストール時には一時的なディレクトリにProxyを生成する
  286.             $createOutputDir false;
  287.             if (is_null($tmpProxyOutputDir)) {
  288.                 $tmpProxyOutputDir sys_get_temp_dir().'/proxy_'.StringUtil::random(12);
  289.                 @mkdir($tmpProxyOutputDir);
  290.                 $createOutputDir true;
  291.             }
  292.             try {
  293.                 if (!$uninstall) {
  294.                     // プラグインmetadata定義を追加
  295.                     $entityDir $this->eccubeConfig['plugin_realdir'].'/'.$plugin->getCode().'/Entity';
  296.                     if (file_exists($entityDir)) {
  297.                         $ormConfig $this->entityManager->getConfiguration();
  298.                         $chain $ormConfig->getMetadataDriverImpl()->getDriver();
  299.                         $driver $ormConfig->newDefaultAnnotationDriver([$entityDir], false);
  300.                         $namespace 'Plugin\\'.$config['code'].'\\Entity';
  301.                         $chain->addDriver($driver$namespace);
  302.                         $ormConfig->addEntityNamespace($plugin->getCode(), $namespace);
  303.                     }
  304.                 }
  305.                 // 一時的に利用するProxyを生成してからスキーマを更新する
  306.                 $generatedFiles $this->regenerateProxy($plugintrue$tmpProxyOutputDir$uninstall);
  307.                 call_user_func($callback$generatedFiles$tmpProxyOutputDir);
  308.             } finally {
  309.                 if ($createOutputDir) {
  310.                     $files Finder::create()
  311.                         ->in($tmpProxyOutputDir)
  312.                         ->files();
  313.                     $f = new Filesystem();
  314.                     $f->remove($files);
  315.                 }
  316.             }
  317.         }
  318.     }
  319.     public function createTempDir()
  320.     {
  321.         $tempDir $this->projectRoot.'/var/cache/'.$this->environment.'/Plugin';
  322.         @mkdir($tempDir);
  323.         $d = ($tempDir.'/'.sha1(StringUtil::random(16)));
  324.         if (!mkdir($d0777)) {
  325.             throw new PluginException(trans('admin.store.plugin.mkdir.error', ['%dir_name%' => $d]));
  326.         }
  327.         return $d;
  328.     }
  329.     public function deleteDirs($arr)
  330.     {
  331.         foreach ($arr as $dir) {
  332.             if (file_exists($dir)) {
  333.                 $fs = new Filesystem();
  334.                 $fs->remove($dir);
  335.             }
  336.         }
  337.     }
  338.     /**
  339.      * @param string $archive
  340.      * @param string $dir
  341.      *
  342.      * @throws PluginException
  343.      */
  344.     public function unpackPluginArchive($archive$dir)
  345.     {
  346.         $extension pathinfo($archivePATHINFO_EXTENSION);
  347.         try {
  348.             if ($extension == 'zip') {
  349.                 $zip = new \ZipArchive();
  350.                 $zip->open($archive);
  351.                 $zip->extractTo($dir);
  352.                 $zip->close();
  353.             } else {
  354.                 $phar = new \PharData($archive);
  355.                 $phar->extractTo($dirnulltrue);
  356.             }
  357.         } catch (\Exception $e) {
  358.             throw new PluginException(trans('pluginservice.text.error.upload_failure'));
  359.         }
  360.     }
  361.     /**
  362.      * @param $dir
  363.      * @param array $config_cache
  364.      *
  365.      * @throws PluginException
  366.      */
  367.     public function checkPluginArchiveContent($dir, array $config_cache = [])
  368.     {
  369.         try {
  370.             if (!empty($config_cache)) {
  371.                 $meta $config_cache;
  372.             } else {
  373.                 $meta $this->readConfig($dir);
  374.             }
  375.         } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
  376.             throw new PluginException($e->getMessage(), $e->getCode(), $e);
  377.         }
  378.         if (!is_array($meta)) {
  379.             throw new PluginException('config.yml not found or syntax error');
  380.         }
  381.         if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
  382.             throw new PluginException('config.yml code empty or invalid_character(\W)');
  383.         }
  384.         if (!isset($meta['name'])) {
  385.             // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
  386.             throw new PluginException('config.yml name empty');
  387.         }
  388.         if (!isset($meta['version'])) {
  389.             // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
  390.             throw new PluginException('config.yml version invalid_character(\W) ');
  391.         }
  392.     }
  393.     /**
  394.      * @param $pluginDir
  395.      *
  396.      * @return array
  397.      *
  398.      * @throws PluginException
  399.      */
  400.     public function readConfig($pluginDir)
  401.     {
  402.         $composerJsonPath $pluginDir.DIRECTORY_SEPARATOR.'composer.json';
  403.         if (file_exists($composerJsonPath) === false) {
  404.             throw new PluginException("${composerJsonPath} not found.");
  405.         }
  406.         $json json_decode(file_get_contents($composerJsonPath), true);
  407.         if ($json === null) {
  408.             throw new PluginException("Invalid json format. [${composerJsonPath}]");
  409.         }
  410.         if (!isset($json['version'])) {
  411.             throw new PluginException("`version` is not defined in ${composerJsonPath}");
  412.         }
  413.         if (!isset($json['extra']['code'])) {
  414.             throw new PluginException("`extra.code` is not defined in ${composerJsonPath}");
  415.         }
  416.         return [
  417.             'code' => $json['extra']['code'],
  418.             'name' => isset($json['description']) ? $json['description'] : $json['extra']['code'],
  419.             'version' => $json['version'],
  420.             'source' => isset($json['extra']['id']) ? $json['extra']['id'] : 0,
  421.         ];
  422.     }
  423.     public function checkSymbolName($string)
  424.     {
  425.         return strlen($string) < 256 && preg_match('/^\w+$/'$string);
  426.         // plugin_nameやplugin_codeに使える文字のチェック
  427.         // a-z A-Z 0-9 _
  428.         // ディレクトリ名などに使われれるので厳しめ
  429.     }
  430.     /**
  431.      * @param string $path
  432.      */
  433.     public function deleteFile($path)
  434.     {
  435.         $f = new Filesystem();
  436.         $f->remove($path);
  437.     }
  438.     public function checkSamePlugin($code)
  439.     {
  440.         /** @var Plugin $Plugin */
  441.         $Plugin $this->pluginRepository->findOneBy(['code' => $code]);
  442.         if ($Plugin && $Plugin->isInitialized()) {
  443.             throw new PluginException('plugin already installed.');
  444.         }
  445.     }
  446.     public function calcPluginDir($code)
  447.     {
  448.         return $this->projectRoot.'/app/Plugin/'.$code;
  449.     }
  450.     /**
  451.      * @param string $d
  452.      *
  453.      * @throws PluginException
  454.      */
  455.     public function createPluginDir($d)
  456.     {
  457.         $b = @mkdir($d);
  458.         if (!$b) {
  459.             throw new PluginException(trans('admin.store.plugin.mkdir.error', ['%dir_name%' => $d]));
  460.         }
  461.     }
  462.     /**
  463.      * @param $meta
  464.      * @param int $source
  465.      *
  466.      * @return Plugin
  467.      *
  468.      * @throws PluginException
  469.      */
  470.     public function registerPlugin($meta$source 0)
  471.     {
  472.         try {
  473.             $p = new Plugin();
  474.             // インストール直後はプラグインは有効にしない
  475.             $p->setName($meta['name'])
  476.                 ->setEnabled(false)
  477.                 ->setVersion($meta['version'])
  478.                 ->setSource($source)
  479.                 ->setCode($meta['code']);
  480.             $this->entityManager->persist($p);
  481.             $this->entityManager->flush();
  482.             $this->pluginApiService->pluginInstalled($p);
  483.         } catch (\Exception $e) {
  484.             throw new PluginException($e->getMessage(), $e->getCode(), $e);
  485.         }
  486.         return $p;
  487.     }
  488.     /**
  489.      * @param $meta
  490.      * @param string $method
  491.      */
  492.     public function callPluginManagerMethod($meta$method)
  493.     {
  494.         $class '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
  495.         if (class_exists($class)) {
  496.             $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
  497.             if (method_exists($installer$method)) {
  498.                 $installer->$method($meta$this->container);
  499.             }
  500.         }
  501.     }
  502.     /**
  503.      * @param Plugin $plugin
  504.      * @param bool $force
  505.      *
  506.      * @return bool
  507.      *
  508.      * @throws \Exception
  509.      */
  510.     public function uninstall(Plugin $plugin$force true)
  511.     {
  512.         $pluginDir $this->calcPluginDir($plugin->getCode());
  513.         $this->cacheUtil->clearCache();
  514.         $config $this->readConfig($pluginDir);
  515.         if ($plugin->isEnabled()) {
  516.             $this->disable($plugin);
  517.         }
  518.         // 初期化されていない場合はPluginManager#uninstall()は実行しない
  519.         if ($plugin->isInitialized()) {
  520.             $this->callPluginManagerMethod($config'uninstall');
  521.         }
  522.         $this->unregisterPlugin($plugin);
  523.         try {
  524.             // スキーマを更新する
  525.             $this->generateProxyAndUpdateSchema($plugin$configtrue);
  526.             // プラグインのネームスペースに含まれるEntityのテーブルを削除する
  527.             $namespace 'Plugin\\'.$plugin->getCode().'\\Entity';
  528.             $this->schemaService->dropTable($namespace);
  529.         } catch (PersistenceMappingException $e) {
  530.         } catch (ORMMappingException $e) {
  531.             // XXX 削除された Bundle が MappingException をスローする場合があるが実害は無いので無視して進める
  532.         }
  533.         if ($force) {
  534.             $this->deleteFile($pluginDir);
  535.             $this->removeAssets($plugin->getCode());
  536.         }
  537.         $this->pluginApiService->pluginUninstalled($plugin);
  538.         return true;
  539.     }
  540.     public function unregisterPlugin(Plugin $p)
  541.     {
  542.         try {
  543.             $em $this->entityManager;
  544.             $em->remove($p);
  545.             $em->flush();
  546.         } catch (\Exception $e) {
  547.             throw $e;
  548.         }
  549.     }
  550.     public function disable(Plugin $plugin)
  551.     {
  552.         return $this->enable($pluginfalse);
  553.     }
  554.     /**
  555.      * Proxyを再生成します.
  556.      *
  557.      * @param Plugin $plugin プラグイン
  558.      * @param boolean $temporary プラグインが無効状態でも一時的に生成するかどうか
  559.      * @param string|null $outputDir 出力先
  560.      * @param bool $uninstall プラグイン削除の場合はtrue
  561.      *
  562.      * @return array 生成されたファイルのパス
  563.      */
  564.     private function regenerateProxy(Plugin $plugin$temporary$outputDir null$uninstall false)
  565.     {
  566.         if (is_null($outputDir)) {
  567.             $outputDir $this->projectRoot.'/app/proxy/entity';
  568.         }
  569.         @mkdir($outputDir);
  570.         $enabledPluginCodes array_map(
  571.             function ($p) { return $p->getCode(); },
  572.             $temporary $this->pluginRepository->findAll() : $this->pluginRepository->findAllEnabled()
  573.         );
  574.         $excludes = [];
  575.         if (!$uninstall && ($temporary || $plugin->isEnabled())) {
  576.             $enabledPluginCodes[] = $plugin->getCode();
  577.         } else {
  578.             $index array_search($plugin->getCode(), $enabledPluginCodes);
  579.             if ($index !== false && $index >= 0) {
  580.                 array_splice($enabledPluginCodes$index1);
  581.                 $excludes = [$this->projectRoot.'/app/Plugin/'.$plugin->getCode().'/Entity'];
  582.             }
  583.         }
  584.         $enabledPluginEntityDirs array_map(function ($code) {
  585.             return $this->projectRoot."/app/Plugin/${code}/Entity";
  586.         }, $enabledPluginCodes);
  587.         return $this->entityProxyService->generate(
  588.             array_merge([$this->projectRoot.'/app/Customize/Entity'], $enabledPluginEntityDirs),
  589.             $excludes,
  590.             $outputDir
  591.         );
  592.     }
  593.     public function enable(Plugin $plugin$enable true)
  594.     {
  595.         $em $this->entityManager;
  596.         try {
  597.             $pluginDir $this->calcPluginDir($plugin->getCode());
  598.             $config $this->readConfig($pluginDir);
  599.             $em->getConnection()->beginTransaction();
  600.             $this->callPluginManagerMethod($config$enable 'enable' 'disable');
  601.             $plugin->setEnabled($enable true false);
  602.             $em->persist($plugin);
  603.             // Proxyだけ再生成してスキーマは更新しない
  604.             $this->regenerateProxy($pluginfalse);
  605.             $em->flush();
  606.             $em->getConnection()->commit();
  607.             if ($enable) {
  608.                 $this->pluginApiService->pluginEnabled($plugin);
  609.             } else {
  610.                 $this->pluginApiService->pluginDisabled($plugin);
  611.             }
  612.         } catch (\Exception $e) {
  613.             $em->getConnection()->rollback();
  614.             throw $e;
  615.         }
  616.         return true;
  617.     }
  618.     /**
  619.      * Update plugin
  620.      *
  621.      * @param Plugin $plugin
  622.      * @param string $path
  623.      *
  624.      * @return bool
  625.      *
  626.      * @throws PluginException
  627.      * @throws \Exception
  628.      */
  629.     public function update(Plugin $plugin$path)
  630.     {
  631.         $tmp null;
  632.         try {
  633.             $this->cacheUtil->clearCache();
  634.             $tmp $this->createTempDir();
  635.             $this->unpackPluginArchive($path$tmp); // 一旦テンポラリに展開
  636.             $this->checkPluginArchiveContent($tmp);
  637.             $config $this->readConfig($tmp);
  638.             if ($plugin->getCode() != $config['code']) {
  639.                 throw new PluginException('new/old plugin code is different.');
  640.             }
  641.             $pluginBaseDir $this->calcPluginDir($config['code']);
  642.             $this->deleteFile($tmp); // テンポラリのファイルを削除
  643.             $this->unpackPluginArchive($path$pluginBaseDir); // 問題なければ本当のplugindirへ
  644.             $this->copyAssets($plugin->getCode());
  645.             $this->updatePlugin($plugin$config); // dbにプラグイン登録
  646.         } catch (PluginException $e) {
  647.             $this->deleteDirs([$tmp]);
  648.             throw $e;
  649.         } catch (\Exception $e) {
  650.             // catch exception of composer
  651.             $this->deleteDirs([$tmp]);
  652.             throw $e;
  653.         }
  654.         return true;
  655.     }
  656.     /**
  657.      * Update plugin
  658.      *
  659.      * @param Plugin $plugin
  660.      * @param array  $meta     Config data
  661.      *
  662.      * @throws \Exception
  663.      */
  664.     public function updatePlugin(Plugin $plugin$meta)
  665.     {
  666.         $em $this->entityManager;
  667.         try {
  668.             $em->getConnection()->beginTransaction();
  669.             $plugin->setVersion($meta['version'])
  670.                 ->setName($meta['name']);
  671.             $em->persist($plugin);
  672.             $this->generateProxyAndUpdateSchema($plugin$meta);
  673.             if ($plugin->isInitialized()) {
  674.                 $this->callPluginManagerMethod($meta'update');
  675.             }
  676.             $this->copyAssets($plugin->getCode());
  677.             $em->flush();
  678.             if ($em->getConnection()->getNativeConnection()->inTransaction()) {
  679.                 $em->getConnection()->commit();
  680.             }
  681.         } catch (\Exception $e) {
  682.             if ($em->getConnection()->getNativeConnection()->inTransaction()) {
  683.                 if ($em->getConnection()->isRollbackOnly()) {
  684.                     $em->getConnection()->rollback();
  685.                 }
  686.             }
  687.             throw $e;
  688.         }
  689.     }
  690.     /**
  691.      * Get array require by plugin
  692.      * Todo: need define dependency plugin mechanism
  693.      *
  694.      * @param array|Plugin $plugin format as plugin from api
  695.      *
  696.      * @return array|mixed
  697.      *
  698.      * @throws PluginException
  699.      */
  700.     public function getPluginRequired($plugin)
  701.     {
  702.         $pluginCode $plugin instanceof Plugin $plugin->getCode() : $plugin['code'];
  703.         $pluginVersion $plugin instanceof Plugin $plugin->getVersion() : $plugin['version'];
  704.         $results = [];
  705.         $this->composerService->foreachRequires('ec-cube/'.strtolower($pluginCode), $pluginVersion, function ($package) use (&$results) {
  706.             $results[] = $package;
  707.         }, 'eccube-plugin');
  708.         return $results;
  709.     }
  710.     /**
  711.      * Find the dependent plugins that need to be disabled
  712.      *
  713.      * @param string $pluginCode
  714.      *
  715.      * @return array plugin code
  716.      */
  717.     public function findDependentPluginNeedDisable($pluginCode)
  718.     {
  719.         return $this->findDependentPlugin($pluginCodetrue);
  720.     }
  721.     /**
  722.      * Find the other plugin that has requires on it.
  723.      * Check in both dtb_plugin table and <PluginCode>/composer.json
  724.      *
  725.      * @param string $pluginCode
  726.      * @param bool   $enableOnly
  727.      *
  728.      * @return array plugin code
  729.      */
  730.     public function findDependentPlugin($pluginCode$enableOnly false)
  731.     {
  732.         $criteria Criteria::create()
  733.             ->where(Criteria::expr()->neq('code'$pluginCode));
  734.         if ($enableOnly) {
  735.             $criteria->andWhere(Criteria::expr()->eq('enabled'Constant::ENABLED));
  736.         }
  737.         /**
  738.          * @var Plugin[]
  739.          */
  740.         $plugins $this->pluginRepository->matching($criteria);
  741.         $dependents = [];
  742.         foreach ($plugins as $plugin) {
  743.             $dir $this->eccubeConfig['plugin_realdir'].'/'.$plugin->getCode();
  744.             $fileName $dir.'/composer.json';
  745.             if (!file_exists($fileName)) {
  746.                 continue;
  747.             }
  748.             $jsonText file_get_contents($fileName);
  749.             if ($jsonText) {
  750.                 $json json_decode($jsonTexttrue);
  751.                 if (!isset($json['require'])) {
  752.                     continue;
  753.                 }
  754.                 if (array_key_exists(self::VENDOR_NAME.'/'.$pluginCode$json['require']) // 前方互換用
  755.                     || array_key_exists(self::VENDOR_NAME.'/'.strtolower($pluginCode), $json['require'])) {
  756.                     $dependents[] = $plugin->getCode();
  757.                 }
  758.             }
  759.         }
  760.         return $dependents;
  761.     }
  762.     /**
  763.      * Get dependent plugin by code
  764.      * It's base on composer.json
  765.      * Return the plugin code and version in the format of the composer
  766.      *
  767.      * @param string   $pluginCode
  768.      * @param int|null $libraryType
  769.      *                      self::ECCUBE_LIBRARY only return library/plugin of eccube
  770.      *                      self::OTHER_LIBRARY only return library/plugin of 3rd part ex: symfony, composer, ...
  771.      *                      default : return all library/plugin
  772.      *
  773.      * @return array format [packageName1 => version1, packageName2 => version2]
  774.      */
  775.     public function getDependentByCode($pluginCode$libraryType null)
  776.     {
  777.         $pluginDir $this->calcPluginDir($pluginCode);
  778.         $jsonFile $pluginDir.'/composer.json';
  779.         if (!file_exists($jsonFile)) {
  780.             return [];
  781.         }
  782.         $jsonText file_get_contents($jsonFile);
  783.         $json json_decode($jsonTexttrue);
  784.         $dependents = [];
  785.         if (isset($json['require'])) {
  786.             $require $json['require'];
  787.             switch ($libraryType) {
  788.                 case self::ECCUBE_LIBRARY:
  789.                     $dependents array_intersect_key($requirearray_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i'array_keys($require))));
  790.                     break;
  791.                 case self::OTHER_LIBRARY:
  792.                     $dependents array_intersect_key($requirearray_flip(preg_grep('/^'.self::VENDOR_NAME.'\//i'array_keys($require), PREG_GREP_INVERT)));
  793.                     break;
  794.                 default:
  795.                     $dependents $json['require'];
  796.                     break;
  797.             }
  798.         }
  799.         return $dependents;
  800.     }
  801.     /**
  802.      * Format array dependent plugin to string
  803.      * It is used for commands.
  804.      *
  805.      * @param array $packages   format [packageName1 => version1, packageName2 => version2]
  806.      * @param bool  $getVersion
  807.      *
  808.      * @return string format if version=true: "packageName1:version1 packageName2:version2", if version=false: "packageName1 packageName2"
  809.      */
  810.     public function parseToComposerCommand(array $packages$getVersion true)
  811.     {
  812.         $result array_keys($packages);
  813.         if ($getVersion) {
  814.             $result array_map(function ($package$version) {
  815.                 return $package.':'.$version;
  816.             }, array_keys($packages), array_values($packages));
  817.         }
  818.         return implode(' '$result);
  819.     }
  820.     /**
  821.      * リソースファイル等をコピー
  822.      * コピー元となるファイルの置き場所は固定であり、
  823.      * [プラグインコード]/Resource/assets
  824.      * 配下に置かれているファイルが所定の位置へコピーされる
  825.      *
  826.      * @param $pluginCode
  827.      */
  828.     public function copyAssets($pluginCode)
  829.     {
  830.         $assetsDir $this->calcPluginDir($pluginCode).'/Resource/assets';
  831.         // プラグインにリソースファイルがあれば所定の位置へコピー
  832.         if (file_exists($assetsDir)) {
  833.             $file = new Filesystem();
  834.             $file->mirror($assetsDir$this->eccubeConfig['plugin_html_realdir'].$pluginCode.'/assets');
  835.         }
  836.     }
  837.     /**
  838.      * コピーしたリソースファイル等を削除
  839.      *
  840.      * @param string $pluginCode
  841.      */
  842.     public function removeAssets($pluginCode)
  843.     {
  844.         $assetsDir $this->eccubeConfig['plugin_html_realdir'].$pluginCode;
  845.         // コピーされているリソースファイルがあれば削除
  846.         if (file_exists($assetsDir)) {
  847.             $file = new Filesystem();
  848.             $file->remove($assetsDir);
  849.         }
  850.     }
  851.     /**
  852.      * Plugin is exist check
  853.      *
  854.      * @param array  $plugins    get from api
  855.      * @param string $pluginCode
  856.      *
  857.      * @return false|int|string
  858.      */
  859.     public function checkPluginExist($plugins$pluginCode)
  860.     {
  861.         if (strpos($pluginCodeself::VENDOR_NAME.'/') !== false) {
  862.             $pluginCode str_replace(self::VENDOR_NAME.'/'''$pluginCode);
  863.         }
  864.         // Find plugin in array
  865.         $index array_search($pluginCodearray_column($plugins'product_code')); // 前方互換用
  866.         if (false === $index) {
  867.             $index array_search(strtolower($pluginCode), array_column($plugins'product_code'));
  868.         }
  869.         return $index;
  870.     }
  871. }