Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 20 |
CRAP | |
0.00% |
0 / 320 |
| PluginService | |
0.00% |
0 / 1 |
|
0.00% |
0 / 20 |
5700 | |
0.00% |
0 / 320 |
| __construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| install | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 27 |
|||
| createTempDir | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 8 |
|||
| deleteDirs | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
| unpackPluginArchive | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 16 |
|||
| checkPluginArchiveContent | |
0.00% |
0 / 1 |
210 | |
0.00% |
0 / 36 |
|||
| readYml | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
| checkSymbolName | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
| deleteFile | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
| checkSamePlugin | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
| calcPluginDir | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| createPluginDir | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
| registerPlugin | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 42 |
|||
| callPluginManagerMethod | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 9 |
|||
| uninstall | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 11 |
|||
| unregisterPlugin | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 16 |
|||
| disable | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| enable | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 19 |
|||
| update | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 33 |
|||
| updatePlugin | |
0.00% |
0 / 1 |
210 | |
0.00% |
0 / 61 |
|||
| <?php | |
| /* | |
| * This file is part of EC-CUBE | |
| * | |
| * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved. | |
| * | |
| * http://www.lockon.co.jp/ | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License | |
| * as published by the Free Software Foundation; either version 2 | |
| * of the License, or (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| */ | |
| namespace Eccube\Service; | |
| use Eccube\Common\Constant; | |
| use Eccube\Exception\PluginException; | |
| use Eccube\Util\Cache; | |
| use Eccube\Util\Str; | |
| use Symfony\Component\Filesystem\Filesystem; | |
| use Symfony\Component\Yaml\Yaml; | |
| class PluginService | |
| { | |
| const CONFIG_YML = 'config.yml'; | |
| const EVENT_YML = 'event.yml'; | |
| private $app; | |
| public function __construct($app) | |
| { | |
| $this->app = $app; | |
| } | |
| public function install($path, $source = 0) | |
| { | |
| $pluginBaseDir = null; | |
| $tmp = null; | |
| try { | |
| $this->app->removePluginConfigCache(); | |
| Cache::clear($this->app, false); | |
| $tmp = $this->createTempDir(); | |
| $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開 | |
| $this->checkPluginArchiveContent($tmp); | |
| $config = $this->readYml($tmp.'/'.self::CONFIG_YML); | |
| $event = $this->readYml($tmp.'/'.self::EVENT_YML); | |
| $this->deleteFile($tmp); // テンポラリのファイルを削除 | |
| $this->checkSamePlugin($config['code']); // 重複していないかチェック | |
| $pluginBaseDir = $this->calcPluginDir($config['code']); | |
| $this->createPluginDir($pluginBaseDir); // 本来の置き場所を作成 | |
| $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ | |
| $this->registerPlugin($config, $event, $source); // dbにプラグイン登録 | |
| $this->app->writePluginConfigCache(); | |
| } catch (PluginException $e) { | |
| $this->deleteDirs(array($tmp, $pluginBaseDir)); | |
| throw $e; | |
| } catch (\Exception $e) { // インストーラがどんなExceptionを上げるかわからないので | |
| $this->deleteDirs(array($tmp, $pluginBaseDir)); | |
| throw $e; | |
| } | |
| return true; | |
| } | |
| public function createTempDir() | |
| { | |
| @mkdir($this->app['config']['plugin_temp_realdir']); | |
| $d = ($this->app['config']['plugin_temp_realdir'].'/'.sha1(Str::random(16))); | |
| if (!mkdir($d, 0777)) { | |
| throw new PluginException($php_errormsg.$d); | |
| } | |
| return $d; | |
| } | |
| public function deleteDirs($arr) | |
| { | |
| foreach ($arr as $dir) { | |
| if (file_exists($dir)) { | |
| $fs = new Filesystem(); | |
| $fs->remove($dir); | |
| } | |
| } | |
| } | |
| public function unpackPluginArchive($archive, $dir) | |
| { | |
| $extension = pathinfo($archive, PATHINFO_EXTENSION); | |
| try { | |
| if ($extension == 'zip') { | |
| $zip = new \ZipArchive(); | |
| $zip->open($archive); | |
| $zip->extractTo($dir); | |
| $zip->close(); | |
| } else { | |
| $phar = new \PharData($archive); | |
| $phar->extractTo($dir, null, true); | |
| } | |
| } catch (\Exception $e) { | |
| throw new PluginException('アップロードに失敗しました。圧縮ファイルを確認してください。'); | |
| } | |
| } | |
| public function checkPluginArchiveContent($dir, array $config_cache = array()) | |
| { | |
| try { | |
| if (!empty($config_cache)) { | |
| $meta = $config_cache; | |
| } else { | |
| $meta = $this->readYml($dir . '/config.yml'); | |
| } | |
| } catch (\Symfony\Component\Yaml\Exception\ParseException $e) { | |
| throw new PluginException($e->getMessage(), $e->getCode(), $e); | |
| } | |
| if (!is_array($meta)) { | |
| throw new PluginException('config.yml not found or syntax error'); | |
| } | |
| if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) { | |
| throw new PluginException('config.yml code empty or invalid_character(\W)'); | |
| } | |
| if (!isset($meta['name'])) { | |
| // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし | |
| throw new PluginException('config.yml name empty'); | |
| } | |
| if (isset($meta['event']) && !$this->checkSymbolName($meta['event'])) { // eventだけは必須ではない | |
| throw new PluginException('config.yml event empty or invalid_character(\W) '); | |
| } | |
| if (!isset($meta['version'])) { | |
| // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし | |
| throw new PluginException('config.yml version invalid_character(\W) '); | |
| } | |
| if (isset($meta['orm.path'])) { | |
| if (!is_array($meta['orm.path'])) { | |
| throw new PluginException('config.yml orm.path invalid_character(\W) '); | |
| } | |
| } | |
| if (isset($meta['service'])) { | |
| if (!is_array($meta['service'])) { | |
| throw new PluginException('config.yml service invalid_character(\W) '); | |
| } | |
| } | |
| } | |
| public function readYml($yml) | |
| { | |
| if (file_exists($yml)) { | |
| return Yaml::parse(file_get_contents($yml)); | |
| } | |
| return false; | |
| } | |
| public function checkSymbolName($string) | |
| { | |
| return strlen($string) < 256 && preg_match('/^\w+$/', $string); | |
| // plugin_nameやplugin_codeに使える文字のチェック | |
| // a-z A-Z 0-9 _ | |
| // ディレクトリ名などに使われれるので厳しめ | |
| } | |
| public function deleteFile($path) | |
| { | |
| $f = new Filesystem(); | |
| $f->remove($path); | |
| } | |
| public function checkSamePlugin($code) | |
| { | |
| $repo = $this->app['eccube.repository.plugin']->findOneBy(array('code' => $code)); | |
| if ($repo) { | |
| throw new PluginException('plugin already installed.'); | |
| } | |
| } | |
| public function calcPluginDir($name) | |
| { | |
| return $this->app['config']['plugin_realdir'].'/'.$name; | |
| } | |
| public function createPluginDir($d) | |
| { | |
| $b = @mkdir($d); | |
| if (!$b) { | |
| throw new PluginException($php_errormsg); | |
| } | |
| } | |
| public function registerPlugin($meta, $event_yml, $source = 0) | |
| { | |
| $em = $this->app['orm.em']; | |
| $em->getConnection()->beginTransaction(); | |
| try { | |
| $p = new \Eccube\Entity\Plugin(); | |
| // インストール直後はプラグインは有効にしない | |
| $p->setName($meta['name']) | |
| ->setEnable(Constant::DISABLED) | |
| ->setClassName(isset($meta['event']) ? $meta['event'] : '') | |
| ->setVersion($meta['version']) | |
| ->setDelflg(Constant::DISABLED) | |
| ->setSource($source) | |
| ->setCode($meta['code']); | |
| $em->persist($p); | |
| $em->flush(); | |
| if (is_array($event_yml)) { | |
| foreach ($event_yml as $event => $handlers) { | |
| foreach ($handlers as $handler) { | |
| if (!$this->checkSymbolName($handler[0])) { | |
| throw new PluginException('Handler name format error'); | |
| } | |
| $peh = new \Eccube\Entity\PluginEventHandler(); | |
| $peh->setPlugin($p) | |
| ->setEvent($event) | |
| ->setdelFlg(Constant::DISABLED) | |
| ->setHandler($handler[0]) | |
| ->setHandlerType($handler[1]) | |
| ->setPriority($this->app['eccube.repository.plugin_event_handler']->calcNewPriority($event, $handler[1])); | |
| $em->persist($peh); | |
| $em->flush(); | |
| } | |
| } | |
| } | |
| $em->persist($p); | |
| $this->callPluginManagerMethod($meta, 'install'); | |
| $em->flush(); | |
| $em->getConnection()->commit(); | |
| } catch (\Exception $e) { | |
| $em->getConnection()->rollback(); | |
| throw new PluginException($e->getMessage()); | |
| } | |
| return $p; | |
| } | |
| public function callPluginManagerMethod($meta, $method) | |
| { | |
| $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager'; | |
| if (class_exists($class)) { | |
| $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する | |
| if (method_exists($installer, $method)) { | |
| $installer->$method($meta, $this->app); | |
| } | |
| } | |
| } | |
| public function uninstall(\Eccube\Entity\Plugin $plugin) | |
| { | |
| $pluginDir = $this->calcPluginDir($plugin->getCode()); | |
| $this->app->removePluginConfigCache(); | |
| Cache::clear($this->app, false); | |
| $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'disable'); | |
| $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'uninstall'); | |
| $this->unregisterPlugin($plugin); | |
| $this->deleteFile($pluginDir); | |
| $this->app->writePluginConfigCache(); | |
| return true; | |
| } | |
| public function unregisterPlugin(\Eccube\Entity\Plugin $p) | |
| { | |
| try { | |
| $em = $this->app['orm.em']; | |
| $em->getConnection()->beginTransaction(); | |
| $p->setDelFlg(Constant::ENABLED)->setEnable(Constant::DISABLED); | |
| foreach ($p->getPluginEventHandlers()->toArray() as $peh) { | |
| $peh->setDelFlg(Constant::ENABLED); | |
| } | |
| $em->persist($p); | |
| $em->flush(); | |
| $em->getConnection()->commit(); | |
| } catch (\Exception $e) { | |
| $em->getConnection()->rollback(); | |
| throw $e; | |
| } | |
| } | |
| public function disable(\Eccube\Entity\Plugin $plugin) | |
| { | |
| return $this->enable($plugin, false); | |
| } | |
| public function enable(\Eccube\Entity\Plugin $plugin, $enable = true) | |
| { | |
| $em = $this->app['orm.em']; | |
| try { | |
| $this->app->removePluginConfigCache(); | |
| Cache::clear($this->app, false); | |
| $pluginDir = $this->calcPluginDir($plugin->getCode()); | |
| $em->getConnection()->beginTransaction(); | |
| $plugin->setEnable($enable ? Constant::ENABLED : Constant::DISABLED); | |
| $em->persist($plugin); | |
| $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), $enable ? 'enable' : 'disable'); | |
| $em->flush(); | |
| $em->getConnection()->commit(); | |
| $this->app->writePluginConfigCache(); | |
| } catch (\Exception $e) { | |
| $em->getConnection()->rollback(); | |
| throw $e; | |
| } | |
| return true; | |
| } | |
| public function update(\Eccube\Entity\Plugin $plugin, $path) | |
| { | |
| $pluginBaseDir = null; | |
| $tmp = null; | |
| try { | |
| $this->app->removePluginConfigCache(); | |
| Cache::clear($this->app, false); | |
| $tmp = $this->createTempDir(); | |
| $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開 | |
| $this->checkPluginArchiveContent($tmp); | |
| $config = $this->readYml($tmp.'/'.self::CONFIG_YML); | |
| $event = $this->readYml($tmp.'/event.yml'); | |
| if ($plugin->getCode() != $config['code']) { | |
| throw new PluginException('new/old plugin code is different.'); | |
| } | |
| if ($plugin->getName() != $config['name']) { | |
| throw new PluginException('new/old plugin name is different.'); | |
| } | |
| $pluginBaseDir = $this->calcPluginDir($config['code']); | |
| $this->deleteFile($tmp); // テンポラリのファイルを削除 | |
| $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ | |
| $this->updatePlugin($plugin, $config, $event); // dbにプラグイン登録 | |
| $this->app->writePluginConfigCache(); | |
| } catch (PluginException $e) { | |
| foreach (array($tmp) as $dir) { | |
| if (file_exists($dir)) { | |
| $fs = new Filesystem(); | |
| $fs->remove($dir); | |
| } | |
| } | |
| throw $e; | |
| } | |
| return true; | |
| } | |
| public function updatePlugin(\Eccube\Entity\Plugin $plugin, $meta, $event_yml) | |
| { | |
| try { | |
| $em = $this->app['orm.em']; | |
| $em->getConnection()->beginTransaction(); | |
| $plugin->setVersion($meta['version']) | |
| ->setName($meta['name']); | |
| if (isset($meta['event'])) { | |
| $plugin->setClassName($meta['event']); | |
| } | |
| $rep = $this->app['eccube.repository.plugin_event_handler']; | |
| if (is_array($event_yml)) { | |
| foreach ($event_yml as $event => $handlers) { | |
| foreach ($handlers as $handler) { | |
| if (!$this->checkSymbolName($handler[0])) { | |
| throw new PluginException('Handler name format error'); | |
| } | |
| // updateで追加されたハンドラかどうか調べる | |
| $peh = $rep->findBy(array('del_flg' => Constant::DISABLED, | |
| 'plugin_id' => $plugin->getId(), | |
| 'event' => $event, | |
| 'handler' => $handler[0], | |
| 'handler_type' => $handler[1],)); | |
| if (!$peh) { // 新規にevent.ymlに定義されたハンドラなのでinsertする | |
| $peh = new \Eccube\Entity\PluginEventHandler(); | |
| $peh->setPlugin($plugin) | |
| ->setEvent($event) | |
| ->setdelFlg(Constant::DISABLED) | |
| ->setHandler($handler[0]) | |
| ->setHandlerType($handler[1]) | |
| ->setPriority($rep->calcNewPriority($event, $handler[1])); | |
| $em->persist($peh); | |
| $em->flush(); | |
| } | |
| } | |
| } | |
| # アップデート後のevent.ymlで削除されたハンドラをdtb_plugin_event_handlerから探して削除 | |
| foreach ($rep->findBy(array('del_flg' => Constant::DISABLED, 'plugin_id' => $plugin->getId())) as $peh) { | |
| if (!isset($event_yml[$peh->getEvent()])) { | |
| $em->remove($peh); | |
| $em->flush(); | |
| } else { | |
| $match = false; | |
| foreach ($event_yml[$peh->getEvent()] as $handler) { | |
| if ($peh->getHandler() == $handler[0] && $peh->getHandlerType() == $handler[1]) { | |
| $match = true; | |
| } | |
| } | |
| if (!$match) { | |
| $em->remove($peh); | |
| $em->flush(); | |
| } | |
| } | |
| } | |
| } | |
| $em->persist($plugin); | |
| $this->callPluginManagerMethod($meta, 'update'); | |
| $em->flush(); | |
| $em->getConnection()->commit(); | |
| } catch (\Exception $e) { | |
| $em->getConnection()->rollback(); | |
| throw $e; | |
| } | |
| } | |
| } |