Failed Conditions
Branch master (23f88c)
by Kentaro
27:22
created

src/Eccube/Service/PluginService.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * This program is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU General Public License
12
 * as published by the Free Software Foundation; either version 2
13
 * of the License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
 */
24
25
namespace Eccube\Service;
26
27
use Eccube\Common\Constant;
28
use Eccube\Exception\PluginException;
29
use Eccube\Util\Str;
30
use Symfony\Component\Filesystem\Filesystem;
31
use Symfony\Component\Yaml\Yaml;
32
33
class PluginService
34
{
35
    const CONFIG_YML = 'config.yml';
36
    const EVENT_YML = 'event.yml';
37
    private $app;
38
39 3
    public function __construct($app)
40
    {
41 3
        $this->app = $app;
42 3
    }
43
44
    public function install($path, $source = 0)
45
    {
46
        $pluginBaseDir = null;
47
        $tmp = null;
48
49
        try {
50
            $tmp = $this->createTempDir();
51
52
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
53
            $this->checkPluginArchiveContent($tmp);
54
55
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
56
            $event = $this->readYml($tmp.'/'.self::EVENT_YML);
57
            $this->deleteFile($tmp); // テンポラリのファイルを削除
58
59
            $this->checkSamePlugin($config['code']); // 重複していないかチェック
60
61
            $pluginBaseDir = $this->calcPluginDir($config['code']);
62
            $this->createPluginDir($pluginBaseDir); // 本来の置き場所を作成
63
64
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
65
66
            $this->registerPlugin($config, $event, $source); // dbにプラグイン登録
67
        } catch (PluginException $e) {
68
            $this->deleteDirs(array($tmp, $pluginBaseDir));
69
            throw $e;
70
        } catch (\Exception $e) { // インストーラがどんなExceptionを上げるかわからないので
71
72
            $this->deleteDirs(array($tmp, $pluginBaseDir));
73
            throw $e;
74
        }
75
76
        return true;
77
    }
78
79
    public function createTempDir()
80
    {
81
        @mkdir($this->app['config']['plugin_temp_realdir']);
82
        $d = ($this->app['config']['plugin_temp_realdir'].'/'.sha1(Str::random(16)));
83
84
        if (!mkdir($d, 0777)) {
85
            throw new PluginException($php_errormsg.$d);
86
        }
87
88
        return $d;
89
    }
90
91
    public function deleteDirs($arr)
92
    {
93
        foreach ($arr as $dir) {
94
            if (file_exists($dir)) {
95
                $fs = new Filesystem();
96
                $fs->remove($dir);
97
            }
98
        }
99
    }
100
101
    public function unpackPluginArchive($archive, $dir)
102
    {
103
        $extension = pathinfo($archive, PATHINFO_EXTENSION);
104
        try {
105
            if ($extension == 'zip') {
106
                $zip = new \ZipArchive();
107
                $zip->open($archive);
108
                $zip->extractTo($dir);
109
                $zip->close();
110
            } else {
111
                $phar = new \PharData($archive);
112
                $phar->extractTo($dir, null, true);
113
            }
114
        } catch (\Exception $e) {
115
            throw new PluginException('アップロードに失敗しました。圧縮ファイルを確認してください。');
116
        }
117
    }
118
119
    public function checkPluginArchiveContent($dir)
120
    {
121
        try {
122
            $meta = $this->readYml($dir . '/config.yml');
0 ignored issues
show
Concat operator must not be surrounded by spaces
Loading history...
123
        } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
124
            $this->app['monolog']->warning($e->getMessage());
125
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
126
        }
127
128
        if (!is_array($meta)) {
129
            throw new PluginException('config.yml not found or syntax error');
130
        }
131 View Code Duplication
        if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
132
            throw new PluginException('config.yml code empty or invalid_character(\W)');
133
        }
134
        if (!isset($meta['name'])) {
135
            // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
136
            throw new PluginException('config.yml name empty');
137
        }
138 View Code Duplication
        if (isset($meta['event']) && !$this->checkSymbolName($meta['event'])) { // eventだけは必須ではない
139
            throw new PluginException('config.yml event empty or invalid_character(\W) ');
140
        }
141
        if (!isset($meta['version'])) {
142
            // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
143
            throw new PluginException('config.yml version invalid_character(\W) ');
144
        }
145
        if (isset($meta['orm.path'])) {
146
            if (!is_array($meta['orm.path'])) {
147
                throw new PluginException('config.yml orm.path invalid_character(\W) ');
148
            }
149
        }
150
        if (isset($meta['service'])) {
151
            if (!is_array($meta['service'])) {
152
                throw new PluginException('config.yml service invalid_character(\W) ');
153
            }
154
        }
155
    }
156
157
    public function readYml($yml)
158
    {
159
        if (file_exists($yml)) {
160
            return Yaml::parse(file_get_contents($yml));
161
        }
162
163
        return false;
164
    }
165
166
    public function checkSymbolName($string)
167
    {
168
        return strlen($string) < 256 && preg_match('/^\w+$/', $string);
169
        // plugin_nameやplugin_codeに使える文字のチェック
170
        // a-z A-Z 0-9 _
171
        // ディレクトリ名などに使われれるので厳しめ
172
    }
173
174
    public function deleteFile($path)
175
    {
176
        $f = new Filesystem();
177
        $f->remove($path);
178
    }
179
180
    public function checkSamePlugin($code)
181
    {
182
        $repo = $this->app['eccube.repository.plugin']->findOneBy(array('code' => $code));
183
        if ($repo) {
184
            throw new PluginException('plugin already installed.');
185
        }
186
    }
187
188
    public function calcPluginDir($name)
189
    {
190
        return $this->app['config']['plugin_realdir'].'/'.$name;
191
    }
192
193
    public function createPluginDir($d)
194
    {
195
        $b = @mkdir($d);
196
        if (!$b) {
197
            throw new PluginException($php_errormsg);
198
        }
199
    }
200
201
    public function registerPlugin($meta, $event_yml, $source = 0)
202
    {
203
        $em = $this->app['orm.em'];
204
        $em->getConnection()->beginTransaction();
205
        try {
206
            $p = new \Eccube\Entity\Plugin();
207
            // インストール直後はプラグインは有効にしない
208
            $p->setName($meta['name'])
209
                ->setEnable(Constant::DISABLED)
210
                ->setClassName(isset($meta['event']) ? $meta['event'] : '')
211
                ->setVersion($meta['version'])
212
                ->setDelflg(Constant::DISABLED)
213
                ->setSource($source)
214
                ->setCode($meta['code']);
215
216
            $em->persist($p);
217
            $em->flush();
218
219
            if (is_array($event_yml)) {
220
                foreach ($event_yml as $event => $handlers) {
221
                    foreach ($handlers as $handler) {
222
                        if (!$this->checkSymbolName($handler[0])) {
223
                            throw new PluginException('Handler name format error');
224
                        }
225
                        $peh = new \Eccube\Entity\PluginEventHandler();
226
                        $peh->setPlugin($p)
227
                            ->setEvent($event)
228
                            ->setdelFlg(Constant::DISABLED)
229
                            ->setHandler($handler[0])
230
                            ->setHandlerType($handler[1])
231
                            ->setPriority($this->app['eccube.repository.plugin_event_handler']->calcNewPriority($event, $handler[1]));
232
                        $em->persist($peh);
233
                        $em->flush();
234
                    }
235
                }
236
            }
237
238
            $em->persist($p);
239
240
            $this->callPluginManagerMethod($meta, 'install');
241
242
            $em->flush();
243
            $em->getConnection()->commit();
244
        } catch (\Exception $e) {
245
            $em->getConnection()->rollback();
246
            throw new PluginException($e->getMessage());
247
        }
248
249
        return $p;
250
    }
251
252
    public function callPluginManagerMethod($meta, $method)
253
    {
254
        $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
255
        if (class_exists($class)) {
256
            $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
257
            if (method_exists($installer, $method)) {
258
                $installer->$method($meta, $this->app);
259
            }
260
        }
261
    }
262
263
    public function uninstall(\Eccube\Entity\Plugin $plugin)
264
    {
265
        $pluginDir = $this->calcPluginDir($plugin->getCode());
266
267
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'disable');
268
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'uninstall');
269
        $this->unregisterPlugin($plugin);
270
        $this->deleteFile($pluginDir);
271
272
        return true;
273
    }
274
275
    public function unregisterPlugin(\Eccube\Entity\Plugin $p)
276
    {
277
        try {
278
            $em = $this->app['orm.em'];
279
            $em->getConnection()->beginTransaction();
280
281
            $p->setDelFlg(Constant::ENABLED)->setEnable(Constant::DISABLED);
282
283
            foreach ($p->getPluginEventHandlers()->toArray() as $peh) {
284
                $peh->setDelFlg(Constant::ENABLED);
285
            }
286
287
            $em->persist($p);
288
            $em->flush();
289
            $em->getConnection()->commit();
290
        } catch (\Exception $e) {
291
            $em->getConnection()->rollback();
292
            throw $e;
293
        }
294
    }
295
296
    public function disable(\Eccube\Entity\Plugin $plugin)
297
    {
298
        return $this->enable($plugin, false);
299
    }
300
301
    public function enable(\Eccube\Entity\Plugin $plugin, $enable = true)
302
    {
303
        $em = $this->app['orm.em'];
304
        try {
305
            $pluginDir = $this->calcPluginDir($plugin->getCode());
306
            $em->getConnection()->beginTransaction();
307
            $plugin->setEnable($enable ? Constant::ENABLED : Constant::DISABLED);
308
            $em->persist($plugin);
309
            $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), $enable ? 'enable' : 'disable');
310
            $em->flush();
311
            $em->getConnection()->commit();
312
        } catch (\Exception $e) {
313
            $em->getConnection()->rollback();
314
            throw $e;
315
        }
316
317
        return true;
318
    }
319
320
    public function update(\Eccube\Entity\Plugin $plugin, $path)
321
    {
322
        $pluginBaseDir = null;
323
        $tmp = null;
324
        try {
325
            $tmp = $this->createTempDir();
326
327
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
328
            $this->checkPluginArchiveContent($tmp);
329
330
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
331
            $event = $this->readYml($tmp.'/event.yml');
332
333
            if ($plugin->getCode() != $config['code']) {
334
                throw new PluginException('new/old plugin code is different.');
335
            }
336
            if ($plugin->getName() != $config['name']) {
337
                throw new PluginException('new/old plugin name is different.');
338
            }
339
340
            $pluginBaseDir = $this->calcPluginDir($config['code']);
341
            $this->deleteFile($tmp); // テンポラリのファイルを削除
342
343
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
344
345
            $this->updatePlugin($plugin, $config, $event); // dbにプラグイン登録
346
        } catch (PluginException $e) {
347
            foreach (array($tmp) as $dir) {
348
                if (file_exists($dir)) {
349
                    $fs = new Filesystem();
350
                    $fs->remove($dir);
351
                }
352
            }
353
            throw $e;
354
        }
355
356
        return true;
357
    }
358
359
    public function updatePlugin(\Eccube\Entity\Plugin $plugin, $meta, $event_yml)
360
    {
361
        try {
362
            $em = $this->app['orm.em'];
363
            $em->getConnection()->beginTransaction();
364
            $plugin->setVersion($meta['version'])
365
                ->setName($meta['name']);
366
367
            if (isset($meta['event'])) {
368
                $plugin->setClassName($meta['event']);
369
            }
370
371
            $rep = $this->app['eccube.repository.plugin_event_handler'];
372
373
            if (is_array($event_yml)) {
374
                foreach ($event_yml as $event => $handlers) {
375
                    foreach ($handlers as $handler) {
376
                        if (!$this->checkSymbolName($handler[0])) {
377
                            throw new PluginException('Handler name format error');
378
                        }
379
                        // updateで追加されたハンドラかどうか調べる
380
                        $peh = $rep->findBy(array('del_flg' => Constant::DISABLED,
381
                            'plugin_id' => $plugin->getId(),
382
                            'event' => $event,
383
                            'handler' => $handler[0],
384
                            'handler_type' => $handler[1],));
385
386
                        if (!$peh) { // 新規にevent.ymlに定義されたハンドラなのでinsertする
387
                            $peh = new \Eccube\Entity\PluginEventHandler();
388
                            $peh->setPlugin($plugin)
389
                                ->setEvent($event)
390
                                ->setdelFlg(Constant::DISABLED)
391
                                ->setHandler($handler[0])
392
                                ->setHandlerType($handler[1])
393
                                ->setPriority($rep->calcNewPriority($event, $handler[1]));
394
                            $em->persist($peh);
395
                            $em->flush();
396
                        }
397
                    }
398
                }
399
400
                # アップデート後のevent.ymlで削除されたハンドラをdtb_plugin_event_handlerから探して削除
401
                foreach ($rep->findBy(array('del_flg' => Constant::DISABLED, 'plugin_id' => $plugin->getId())) as $peh) {
402
                    if (!isset($event_yml[$peh->getEvent()])) {
403
                        $em->remove($peh);
404
                        $em->flush();
405
                    } else {
406
                        $match = false;
407
                        foreach ($event_yml[$peh->getEvent()] as $handler) {
408
                            if ($peh->getHandler() == $handler[0] && $peh->getHandlerType() == $handler[1]) {
409
                                $match = true;
410
                            }
411
                        }
412
                        if (!$match) {
413
                            $em->remove($peh);
414
                            $em->flush();
415
                        }
416
                    }
417
                }
418
            }
419
420
            $em->persist($plugin);
421
            $this->callPluginManagerMethod($meta, 'update');
422
            $em->flush();
423
            $em->getConnection()->commit();
424
        } catch (\Exception $e) {
425
            $em->getConnection()->rollback();
426
            throw $e;
427
        }
428
    }
429
}
430