Completed
Push — 4.0 ( 268f2c...88f012 )
by Hideki
05:48 queued 10s
created

Eccube/Controller/Admin/Store/PluginController.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) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.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\Controller\Admin\Store;
15
16
use Eccube\Common\Constant;
17
use Eccube\Controller\AbstractController;
18
use Eccube\Entity\BaseInfo;
19
use Eccube\Entity\Plugin;
20
use Eccube\Exception\PluginApiException;
21
use Eccube\Exception\PluginException;
22
use Eccube\Form\Type\Admin\AuthenticationType;
23
use Eccube\Form\Type\Admin\PluginLocalInstallType;
24
use Eccube\Form\Type\Admin\PluginManagementType;
25
use Eccube\Repository\BaseInfoRepository;
26
use Eccube\Repository\PluginRepository;
27
use Eccube\Service\Composer\ComposerServiceInterface;
28
use Eccube\Service\PluginApiService;
29
use Eccube\Service\PluginService;
30
use Eccube\Service\SystemService;
31
use Eccube\Util\CacheUtil;
32
use Eccube\Util\StringUtil;
33
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
34
use Symfony\Component\DependencyInjection\Container;
35
use Symfony\Component\Filesystem\Filesystem;
36
use Symfony\Component\Finder\Finder;
37
use Symfony\Component\HttpFoundation\File\UploadedFile;
38
use Symfony\Component\HttpFoundation\JsonResponse;
39
use Symfony\Component\HttpFoundation\RedirectResponse;
40
use Symfony\Component\HttpFoundation\Request;
41
use Symfony\Component\Routing\Annotation\Route;
42
use Symfony\Component\Routing\Exception\RouteNotFoundException;
43
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
44
45
class PluginController extends AbstractController
46
{
47
    /**
48
     * @var PluginService
49
     */
50
    protected $pluginService;
51
52
    /**
53
     * @var BaseInfo
54
     */
55
    protected $BaseInfo;
56
57
    /**
58
     * @var PluginRepository
59
     */
60
    protected $pluginRepository;
61
62
    /**
63
     * @var PluginApiService
64
     */
65
    protected $pluginApiService;
66
67
    /**
68
     * @var ComposerServiceInterface
69
     */
70
    private $composerService;
71
72
    /**
73
     * @var SystemService
74
     */
75
    private $systemService;
76
77
    /**
78
     * PluginController constructor.
79
     *
80
     * @param PluginRepository $pluginRepository
81
     * @param PluginService $pluginService
82
     * @param BaseInfoRepository $baseInfoRepository
83
     * @param PluginApiService $pluginApiService
84
     * @param ComposerServiceInterface $composerService
85
     *
86
     * @throws \Doctrine\ORM\NoResultException
87
     * @throws \Doctrine\ORM\NonUniqueResultException
88
     */
89
    public function __construct(
90
        PluginRepository $pluginRepository,
91
        PluginService $pluginService,
92
        BaseInfoRepository $baseInfoRepository,
93
        PluginApiService $pluginApiService,
94
        ComposerServiceInterface $composerService,
95
        SystemService $systemService
96
    ) {
97
        $this->pluginRepository = $pluginRepository;
98
        $this->pluginService = $pluginService;
99
        $this->BaseInfo = $baseInfoRepository->get();
100
        $this->pluginApiService = $pluginApiService;
101
        $this->composerService = $composerService;
102
        $this->systemService = $systemService;
103
    }
104
105
    /**
106
     * インストール済プラグイン画面
107
     *
108
     * @Route("/%eccube_admin_route%/store/plugin", name="admin_store_plugin")
109
     * @Template("@admin/Store/plugin.twig")
110
     *
111
     * @return array
112
     *
113
     * @throws PluginException
114
     */
115
    public function index()
116
    {
117
        $pluginForms = [];
118
        $configPages = [];
119
        $Plugins = $this->pluginRepository->findBy([], ['code' => 'ASC']);
120
121
        // ファイル設置プラグインの取得.
122
        $unregisteredPlugins = $this->getUnregisteredPlugins($Plugins);
123
        $unregisteredPluginsConfigPages = [];
124
        foreach ($unregisteredPlugins as $unregisteredPlugin) {
125
            try {
126
                $code = $unregisteredPlugin['code'];
127
                // プラグイン用設定画面があれば表示(プラグイン用のサービスプロバイダーに定義されているか)
128
                $unregisteredPluginsConfigPages[$code] = $this->generateUrl('plugin_'.$code.'_config');
129
            } catch (RouteNotFoundException $e) {
130
                // プラグインで設定画面のルートが定義されていない場合は無視
131
            }
132
        }
133
134
        $officialPlugins = [];
135
        $unofficialPlugins = [];
136
137
        foreach ($Plugins as $Plugin) {
138
            $form = $this->formFactory
139
                ->createNamedBuilder(
140
                    'form'.$Plugin->getId(),
141
                    PluginManagementType::class,
142
                    null,
143
                    [
144
                        'plugin_id' => $Plugin->getId(),
145
                    ]
146
                )
147
                ->getForm();
148
            $pluginForms[$Plugin->getId()] = $form->createView();
149
150
            try {
151
                // プラグイン用設定画面があれば表示(プラグイン用のサービスプロバイダーに定義されているか)
152
                $configPages[$Plugin->getCode()] = $this->generateUrl(Container::underscore($Plugin->getCode()).'_admin_config');
153
            } catch (\Exception $e) {
154
                // プラグインで設定画面のルートが定義されていない場合は無視
155
            }
156
            if ($Plugin->getSource() == 0) {
157
                // 商品IDが設定されていない場合、非公式プラグイン
158
                $unofficialPlugins[] = $Plugin;
159
            } else {
160
                $officialPlugins[$Plugin->getSource()] = $Plugin;
161
            }
162
        }
163
164
        // オーナーズストア通信
165
        $officialPluginsDetail = [];
166
        try {
167
            $data = $this->pluginApiService->getPurchased();
168
            foreach ($data as $item) {
169
                if (isset($officialPlugins[$item['id']]) === false) {
170
                    $Plugin = new Plugin();
171
                    $Plugin->setName($item['name']);
172
                    $Plugin->setCode($item['code']);
173
                    $Plugin->setVersion($item['version']);
174
                    $Plugin->setSource($item['id']);
175
                    $Plugin->setEnabled(false);
176
                    $officialPlugins[$item['id']] = $Plugin;
177
                }
178
                $officialPluginsDetail[$item['id']] = $item;
179
            }
180
        } catch (PluginApiException $e) {
181
            $this->addWarning($e->getMessage(), 'admin');
182
        }
183
184
        return [
185
            'plugin_forms' => $pluginForms,
186
            'officialPlugins' => $officialPlugins,
187
            'unofficialPlugins' => $unofficialPlugins,
188
            'configPages' => $configPages,
189
            'unregisteredPlugins' => $unregisteredPlugins,
190
            'unregisteredPluginsConfigPages' => $unregisteredPluginsConfigPages,
191
            'officialPluginsDetail' => $officialPluginsDetail,
192
        ];
193
    }
194
195
    /**
196
     * インストール済プラグインからのアップデート
197
     *
198
     * @Route("/%eccube_admin_route%/store/plugin/{id}/update", requirements={"id" = "\d+"}, name="admin_store_plugin_update", methods={"POST"})
199
     *
200
     * @param Request $request
201
     * @param Plugin $Plugin
202
     * @param CacheUtil $cacheUtil
203
     *
204
     * @return RedirectResponse
205
     */
206
    public function update(Request $request, Plugin $Plugin, CacheUtil $cacheUtil)
207
    {
208
        $form = $this->formFactory
209
            ->createNamedBuilder(
210
                'form'.$Plugin->getId(),
211
                PluginManagementType::class,
212
                null,
213
                [
214
                    'plugin_id' => null, // placeHolder
215
                ]
216
            )
217
            ->getForm();
218
219
        $message = '';
220
        $form->handleRequest($request);
221
        if ($form->isSubmitted() && $form->isValid()) {
222
            $tmpDir = null;
223
            try {
224
                $cacheUtil->clearCache();
225
                $formFile = $form['plugin_archive']->getData();
226
                $tmpDir = $this->pluginService->createTempDir();
227
                $tmpFile = sha1(StringUtil::random(32)).'.'.$formFile->getClientOriginalExtension();
228
                $formFile->move($tmpDir, $tmpFile);
229
                $this->pluginService->update($Plugin, $tmpDir.'/'.$tmpFile);
230
                $fs = new Filesystem();
231
                $fs->remove($tmpDir);
232
                $this->addSuccess(trans('admin.store.plugin.update.complete', ['%plugin_name%' => $Plugin->getName()]), 'admin');
233
234
                return $this->redirectToRoute('admin_store_plugin');
235
            } catch (PluginException $e) {
236
                if (!empty($tmpDir) && file_exists($tmpDir)) {
237
                    $fs = new Filesystem();
238
                    $fs->remove($tmpDir);
239
                }
240
                $message = $e->getMessage();
241
            } catch (\Exception $er) {
242
                // Catch composer install error | Other error
243
                if (!empty($tmpDir) && file_exists($tmpDir)) {
244
                    $fs = new Filesystem();
245
                    $fs->remove($tmpDir);
246
                }
247
                log_error('plugin install failed.', ['original-message' => $er->getMessage()]);
248
                $message = trans('admin.store.plugin.update.failed', ['%plugin_name%' => $Plugin->getName()]);
249
            }
250
        } else {
251
            $errors = $form->getErrors(true);
252
            foreach ($errors as $error) {
253
                $message = $error->getMessage();
254
            }
255
        }
256
257
        $this->addError($message, 'admin');
258
259
        return $this->redirectToRoute('admin_store_plugin');
260
    }
261
262
    /**
263
     * 対象のプラグインを有効にします。
264
     *
265
     * @Route("/%eccube_admin_route%/store/plugin/{id}/enable", requirements={"id" = "\d+"}, name="admin_store_plugin_enable", methods={"POST"})
266
     *
267
     * @param Plugin $Plugin
268
     *
269
     * @return RedirectResponse|JsonResponse
270
     *
271
     * @throws PluginException
272
     */
273
    public function enable(Plugin $Plugin, CacheUtil $cacheUtil, Request $request)
274
    {
275
        $this->isTokenValid();
276
        // QueryString maintenance_modeがない場合
277
        if (!$request->query->has('maintenance_mode')) {
278
            // プラグイン管理の有効ボタンを押したとき
279
            $this->systemService->switchMaintenance(true); // auto_maintenanceと設定されたファイルを生成
280
            // TERMINATE時のイベントを設定
281
            $this->systemService->disableMaintenance(SystemService::AUTO_MAINTENANCE);
282
        } else {
283
            // プラグイン管理のアップデートを実行したとき
284
            // TERMINATE時のイベントを設定
285
            $this->systemService->disableMaintenance(SystemService::AUTO_MAINTENANCE_UPDATE);
286
        }
287
        $cacheUtil->clearCache();
288
289
        $log = null;
290
291
        if ($Plugin->isEnabled()) {
292
            if ($request->isXmlHttpRequest()) {
293
                return $this->json(['success' => true]);
294
            } else {
295
                $this->addError(trans('admin.store.plugin.already.enabled', ['%plugin_name%' => $Plugin->getName()]), 'admin');
296
297
                return $this->redirectToRoute('admin_store_plugin');
298
            }
299
        } else {
300
            // ストアからインストールしたプラグインは依存プラグインが有効化されているかを確認
301
            if ($Plugin->getSource()) {
302
                $requires = $this->pluginService->getPluginRequired($Plugin);
303 View Code Duplication
                $requires = array_filter($requires, function ($req) {
304
                    $code = preg_replace('/^ec-cube\//', '', $req['name']);
305
                    /** @var Plugin $DependPlugin */
306
                    $DependPlugin = $this->pluginRepository->findOneBy(['code' => $code]);
307
308
                    return $DependPlugin->isEnabled() == false;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
309
                });
310
                if (!empty($requires)) {
311
                    $names = array_map(function ($req) {
312
                        return "「${req['description']}」";
313
                    }, $requires);
314
                    $message = trans('%depend_name%を先に有効化してください。', ['%name%' => $Plugin->getName(), '%depend_name%' => implode(', ', $names)]);
315
316 View Code Duplication
                    if ($request->isXmlHttpRequest()) {
317
                        return $this->json(['success' => false, 'message' => $message], 400);
318
                    } else {
319
                        $this->addError($message, 'admin');
320
321
                        return $this->redirectToRoute('admin_store_plugin');
322
                    }
323
                }
324
            }
325
326
            try {
327
                ob_start();
328
329
                if (!$Plugin->isInitialized()) {
330
                    $this->pluginService->installWithCode($Plugin->getCode());
331
                }
332
333
                $this->pluginService->enable($Plugin);
334
            } finally {
335
                $log = ob_get_clean();
336
                while (ob_get_level() > 0) {
337
                    ob_end_flush();
338
                }
339
            }
340
        }
341
342
        if ($request->isXmlHttpRequest()) {
343
            return $this->json(['success' => true, 'log' => $log]);
344
        } else {
345
            $this->addSuccess(trans('admin.store.plugin.enable.complete', ['%plugin_name%' => $Plugin->getName()]), 'admin');
346
347
            return $this->redirectToRoute('admin_store_plugin');
348
        }
349
    }
350
351
    /**
352
     * 対象のプラグインを無効にします。
353
     *
354
     * @Route("/%eccube_admin_route%/store/plugin/{id}/disable", requirements={"id" = "\d+"}, name="admin_store_plugin_disable", methods={"POST"})
355
     *
356
     * @param Request $request
357
     * @param Plugin $Plugin
358
     * @param CacheUtil $cacheUtil
359
     *
360
     * @return \Symfony\Component\HttpFoundation\JsonResponse|RedirectResponse
361
     */
362
    public function disable(Request $request, Plugin $Plugin, CacheUtil $cacheUtil)
363
    {
364
        $this->isTokenValid();
365
366
        // QueryString maintenance_modeであるか確認
367
        $mentenance_mode = $request->query->get('maintenance_mode');
368
369
        // プラグイン管理でアップデートが実行されたとき
370
        if (SystemService::AUTO_MAINTENANCE_UPDATE == $mentenance_mode) {
371
            $this->systemService->switchMaintenance(true, SystemService::AUTO_MAINTENANCE_UPDATE); // auto_maintenance_updateと設定されたファイルを生成
372
        } else {
373
            // プラグイン管理で無効ボタンを押したとき
374
            $this->systemService->switchMaintenance(true); // auto_maintenanceと設定されたファイルを生成
375
            // TERMINATE時のイベントを設定
376
            $this->systemService->disableMaintenance(SystemService::AUTO_MAINTENANCE);
377
        }
378
379
        $cacheUtil->clearCache();
380
381
        $log = null;
382
        if ($Plugin->isEnabled()) {
383
            $dependents = $this->pluginService->findDependentPluginNeedDisable($Plugin->getCode());
384
            if (!empty($dependents)) {
385
                $dependName = $dependents[0];
386
                $DependPlugin = $this->pluginRepository->findOneBy(['code' => $dependents[0]]);
387
                if ($DependPlugin) {
388
                    $dependName = $DependPlugin->getName();
389
                }
390
                $message = trans('admin.plugin.disable.depend', ['%name%' => $Plugin->getName(), '%depend_name%' => $dependName]);
391
392 View Code Duplication
                if ($request->isXmlHttpRequest()) {
393
                    return $this->json(['message' => $message], 400);
394
                } else {
395
                    $this->addError($message, 'admin');
396
397
                    return $this->redirectToRoute('admin_store_plugin');
398
                }
399
            }
400
401
            try {
402
                ob_start();
403
                $this->pluginService->disable($Plugin);
404
            } finally {
405
                $log = ob_get_clean();
406
                while (ob_get_level() > 0) {
407
                    ob_end_flush();
408
                }
409
            }
410
        } else {
411
            if ($request->isXmlHttpRequest()) {
412
                return $this->json(['success' => true, 'log' => $log]);
413
            } else {
414
                $this->addError(trans('admin.store.plugin.already.disabled', ['%plugin_name%' => $Plugin->getName()]), 'admin');
415
416
                return $this->redirectToRoute('admin_store_plugin');
417
            }
418
        }
419
420
        if ($request->isXmlHttpRequest()) {
421
            return $this->json(['success' => true, 'log' => $log]);
422
        } else {
423
            $this->addSuccess(trans('admin.store.plugin.disable.complete', ['%plugin_name%' => $Plugin->getName()]), 'admin');
424
425
            return $this->redirectToRoute('admin_store_plugin');
426
        }
427
    }
428
429
    /**
430
     * 対象のプラグインを削除します。
431
     *
432
     * @Route("/%eccube_admin_route%/store/plugin/{id}/uninstall", requirements={"id" = "\d+"}, name="admin_store_plugin_uninstall", methods={"DELETE"})
433
     *
434
     * @param Plugin $Plugin
435
     * @param CacheUtil $cacheUtil
436
     *
437
     * @return RedirectResponse
438
     *
439
     * @throws \Exception
440
     */
441
    public function uninstall(Plugin $Plugin, CacheUtil $cacheUtil)
442
    {
443
        $this->isTokenValid();
444
445
        if ($Plugin->isEnabled()) {
446
            $this->addError('admin.plugin.uninstall.error.not_disable', 'admin');
447
448
            return $this->redirectToRoute('admin_store_plugin');
449
        }
450
451
        // Check other plugin depend on it
452
        $pluginCode = $Plugin->getCode();
453
        $otherDepend = $this->pluginService->findDependentPlugin($pluginCode);
454
        if (!empty($otherDepend)) {
455
            $DependPlugin = $this->pluginRepository->findOneBy(['code' => $otherDepend[0]]);
456
            $dependName = $otherDepend[0];
457
            if ($DependPlugin) {
458
                $dependName = $DependPlugin->getName();
459
            }
460
            $message = trans('admin.plugin.uninstall.depend', ['%name%' => $Plugin->getName(), '%depend_name%' => $dependName]);
461
            $this->addError($message, 'admin');
462
463
            return $this->redirectToRoute('admin_store_plugin');
464
        }
465
466
        $cacheUtil->clearCache();
467
468
        $this->pluginService->uninstall($Plugin);
469
        $this->addSuccess('admin.store.plugin.uninstall.complete', 'admin');
470
471
        return $this->redirectToRoute('admin_store_plugin');
472
    }
473
474
    /**
475
     * プラグインファイルアップロード画面
476
     *
477
     * @Route("/%eccube_admin_route%/store/plugin/install", name="admin_store_plugin_install")
478
     * @Template("@admin/Store/plugin_install.twig")
479
     *
480
     * @param Request $request
481
     * @param CacheUtil $cacheUtil
482
     *
483
     * @return array|RedirectResponse
484
     */
485
    public function install(Request $request, CacheUtil $cacheUtil)
486
    {
487
        $form = $this->formFactory
488
            ->createBuilder(PluginLocalInstallType::class)
489
            ->getForm();
490
        $errors = [];
491
        $form->handleRequest($request);
492
        if ($form->isSubmitted() && $form->isValid()) {
493
            $tmpDir = null;
494
            try {
495
                $cacheUtil->clearCache();
496
497
                /** @var UploadedFile $formFile */
498
                $formFile = $form['plugin_archive']->getData();
499
                $tmpDir = $this->pluginService->createTempDir();
500
                // 拡張子を付けないとpharが動かないので付ける
501
                $tmpFile = sha1(StringUtil::random(32)).'.'.$formFile->getClientOriginalExtension();
502
                $formFile->move($tmpDir, $tmpFile);
503
                $tmpPath = $tmpDir.'/'.$tmpFile;
504
                $this->pluginService->install($tmpPath);
505
                // Remove tmp file
506
                $fs = new Filesystem();
507
                $fs->remove($tmpDir);
508
                $this->addSuccess('admin.store.plugin.install.complete', 'admin');
509
510
                return $this->redirectToRoute('admin_store_plugin');
511
            } catch (PluginException $e) {
512
                if (!empty($tmpDir) && file_exists($tmpDir)) {
513
                    $fs = new Filesystem();
514
                    $fs->remove($tmpDir);
515
                }
516
                log_error('plugin install failed.', ['original-message' => $e->getMessage()]);
517
                $errors[] = $e;
518
            } catch (\Exception $er) {
519
                // Catch composer install error | Other error
520
                if (!empty($tmpDir) && file_exists($tmpDir)) {
521
                    $fs = new Filesystem();
522
                    $fs->remove($tmpDir);
523
                }
524
                log_error('plugin install failed.', ['original-message' => $er->getMessage()]);
525
                $this->addError('admin.store.plugin.install.failed', 'admin');
526
            }
527
        } else {
528
            foreach ($form->getErrors(true) as $error) {
529
                $errors[] = $error;
530
            }
531
        }
532
533
        return [
534
            'form' => $form->createView(),
535
            'errors' => $errors,
536
        ];
537
    }
538
539
    /**
540
     * 認証キー設定画面
541
     *
542
     * @Route("/%eccube_admin_route%/store/plugin/authentication_setting", name="admin_store_authentication_setting")
543
     * @Template("@admin/Store/authentication_setting.twig")
544
     *
545
     * @param Request $request
546
     *
547
     * @return array
548
     */
549
    public function authenticationSetting(Request $request, CacheUtil $cacheUtil)
550
    {
551
        $builder = $this->formFactory
552
            ->createBuilder(AuthenticationType::class, $this->BaseInfo);
553
554
        $form = $builder->getForm();
555
        $form->handleRequest($request);
556
557
        if ($form->isSubmitted() && $form->isValid()) {
558
            // 認証キーの登録 and PHP path
559
            $this->BaseInfo = $form->getData();
560
            $this->entityManager->persist($this->BaseInfo);
561
            $this->entityManager->flush();
562
563
            // composerの認証を更新
564
            $this->composerService->configureRepository($this->BaseInfo);
565
            $this->addSuccess('admin.common.save_complete', 'admin');
566
            $cacheUtil->clearCache();
567
568
            return $this->redirectToRoute('admin_store_authentication_setting');
569
        }
570
571
        return [
572
            'form' => $form->createView(),
573
            'eccubeUrl' => $this->generateUrl('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL),
574
            'eccubeShopName' => $this->BaseInfo->getShopName(),
575
        ];
576
    }
577
578
    /**
579
     * フォルダ設置のみのプラグインを取得する.
580
     *
581
     * @param array $plugins
582
     *
583
     * @return array
584
     *
585
     * @throws PluginException
586
     */
587
    protected function getUnregisteredPlugins(array $plugins)
588
    {
589
        $finder = new Finder();
590
        $pluginCodes = [];
591
592
        // DB登録済みプラグインコードのみ取得
593
        foreach ($plugins as $key => $plugin) {
594
            $pluginCodes[] = $plugin->getCode();
595
        }
596
        // DB登録済みプラグインコードPluginディレクトリから排他
597
        $dirs = $finder->in($this->eccubeConfig['plugin_realdir'])->depth(0)->directories();
598
599
        // プラグイン基本チェック
600
        $unregisteredPlugins = [];
601
        foreach ($dirs as $dir) {
602
            $pluginCode = $dir->getBasename();
603
            if (in_array($pluginCode, $pluginCodes, true)) {
604
                continue;
605
            }
606
            try {
607
                $this->pluginService->checkPluginArchiveContent($dir->getRealPath());
608
            } catch (PluginException $e) {
609
                //config.yamlに不備があった際は全てスキップ
610
                log_warning($e->getMessage());
611
                continue;
612
            }
613
            $config = $this->pluginService->readConfig($dir->getRealPath());
614
            $unregisteredPlugins[$pluginCode]['name'] = isset($config['name']) ? $config['name'] : null;
615
            $unregisteredPlugins[$pluginCode]['event'] = isset($config['event']) ? $config['event'] : null;
616
            $unregisteredPlugins[$pluginCode]['version'] = isset($config['version']) ? $config['version'] : null;
617
            $unregisteredPlugins[$pluginCode]['enabled'] = Constant::DISABLED;
618
            $unregisteredPlugins[$pluginCode]['code'] = isset($config['code']) ? $config['code'] : null;
619
        }
620
621
        return $unregisteredPlugins;
622
    }
623
}
624