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; | |
} | |
} | |
} |