Failed Conditions
Push — dev/recommend-plugins ( 37bfa8...e0eb38 )
by Kiyotaka
06:36
created

PluginController::index()   D

Complexity

Conditions 9
Paths 320

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 320
nop 0
dl 0
loc 84
rs 4.9268
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.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\PluginApiService;
28
use Eccube\Service\PluginService;
29
use Eccube\Util\CacheUtil;
30
use Eccube\Util\StringUtil;
31
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
32
use Symfony\Component\DependencyInjection\Container;
33
use Symfony\Component\Filesystem\Filesystem;
34
use Symfony\Component\Finder\Finder;
35
use Symfony\Component\HttpFoundation\File\UploadedFile;
36
use Symfony\Component\HttpFoundation\JsonResponse;
37
use Symfony\Component\HttpFoundation\RedirectResponse;
38
use Symfony\Component\HttpFoundation\Request;
39
use Symfony\Component\Routing\Annotation\Route;
40
use Symfony\Component\Routing\Exception\RouteNotFoundException;
41
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
42
43
class PluginController extends AbstractController
44
{
45
    /**
46
     * @var PluginService
47
     */
48
    protected $pluginService;
49
50
    /**
51
     * @var BaseInfo
52
     */
53
    protected $BaseInfo;
54
55
    /**
56
     * @var PluginRepository
57
     */
58
    protected $pluginRepository;
59
60
    /**
61
     * @var PluginApiService
62
     */
63
    protected $pluginApiService;
64
65
    /**
66
     * PluginController constructor.
67
     *
68
     * @param PluginRepository $pluginRepository
69
     * @param PluginService $pluginService
70
     * @param BaseInfoRepository $baseInfoRepository
71
     * @param PluginApiService $pluginApiService
72
     *
73
     * @throws \Doctrine\ORM\NoResultException
74
     * @throws \Doctrine\ORM\NonUniqueResultException
75
     */
76
    public function __construct(PluginRepository $pluginRepository, PluginService $pluginService, BaseInfoRepository $baseInfoRepository, PluginApiService $pluginApiService)
77
    {
78
        $this->pluginRepository = $pluginRepository;
79
        $this->pluginService = $pluginService;
80
        $this->BaseInfo = $baseInfoRepository->get();
81
        $this->pluginApiService = $pluginApiService;
82
    }
83
84
    /**
85
     * インストール済プラグイン画面
86
     *
87
     * @Route("/%eccube_admin_route%/store/plugin", name="admin_store_plugin")
88
     * @Template("@admin/Store/plugin.twig")
89
     *
90
     * @return array
91
     *
92
     * @throws PluginException
93
     */
94
    public function index()
95
    {
96
        $pluginForms = [];
97
        $configPages = [];
98
        $Plugins = $this->pluginRepository->findBy([], ['code' => 'ASC']);
99
100
        // ファイル設置プラグインの取得.
101
        $unregisteredPlugins = $this->getUnregisteredPlugins($Plugins);
102
        $unregisteredPluginsConfigPages = [];
103
        foreach ($unregisteredPlugins as $unregisteredPlugin) {
104
            try {
105
                $code = $unregisteredPlugin['code'];
106
                // プラグイン用設定画面があれば表示(プラグイン用のサービスプロバイダーに定義されているか)
107
                $unregisteredPluginsConfigPages[$code] = $this->generateUrl('plugin_'.$code.'_config');
108
            } catch (RouteNotFoundException $e) {
109
                // プラグインで設定画面のルートが定義されていない場合は無視
110
            }
111
        }
112
113
        $officialPlugins = [];
114
        $unofficialPlugins = [];
115
116
        foreach ($Plugins as $Plugin) {
117
            $form = $this->formFactory
118
                ->createNamedBuilder(
119
                    'form'.$Plugin->getId(),
120
                    PluginManagementType::class,
121
                    null,
122
                    [
123
                        'plugin_id' => $Plugin->getId(),
124
                    ]
125
                )
126
                ->getForm();
127
            $pluginForms[$Plugin->getId()] = $form->createView();
128
129
            try {
130
                // プラグイン用設定画面があれば表示(プラグイン用のサービスプロバイダーに定義されているか)
131
                $configPages[$Plugin->getCode()] = $this->generateUrl(Container::underscore($Plugin->getCode()).'_admin_config');
132
            } catch (\Exception $e) {
133
                // プラグインで設定画面のルートが定義されていない場合は無視
134
            }
135
            if ($Plugin->getSource() == 0) {
136
                // 商品IDが設定されていない場合、非公式プラグイン
137
                $unofficialPlugins[] = $Plugin;
138
            } else {
139
                $officialPlugins[$Plugin->getSource()] = $Plugin;
140
            }
141
        }
142
143
        // オーナーズストア通信
144
        $officialPluginsDetail = [];
145
        try {
146
            $data = $this->pluginApiService->getPurchased();
147
            foreach ($data as $item) {
148
                $Plugin = null;
149
150
                if (isset($officialPlugins[$item['id']])) {
151
                    $Plugin = $officialPlugins[$item['id']];
0 ignored issues
show
Unused Code introduced by
$Plugin is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
152
                } else {
153
                    $Plugin = new Plugin();
154
                    $Plugin->setName($item['name']);
155
                    $Plugin->setCode($item['code']);
156
                    $Plugin->setVersion($item['version']);
157
                    $Plugin->setSource($item['id']);
158
                    $Plugin->setEnabled(false);
159
                    $officialPlugins[$item['id']] = $Plugin;
160
                }
161
162
                $officialPluginsDetail[$item['id']] = $item;
163
            }
164
        } catch (PluginApiException $e) {
165
            $this->addWarning($e->getMessage(), 'admin');
166
        }
167
168
        return [
169
            'plugin_forms' => $pluginForms,
170
            'officialPlugins' => $officialPlugins,
171
            'unofficialPlugins' => $unofficialPlugins,
172
            'configPages' => $configPages,
173
            'unregisteredPlugins' => $unregisteredPlugins,
174
            'unregisteredPluginsConfigPages' => $unregisteredPluginsConfigPages,
175
            'officialPluginsDetail' => $officialPluginsDetail,
176
        ];
177
    }
178
179
    /**
180
     * インストール済プラグインからのアップデート
181
     *
182
     * @Route("/%eccube_admin_route%/store/plugin/{id}/update", requirements={"id" = "\d+"}, name="admin_store_plugin_update", methods={"POST"})
183
     *
184
     * @param Request $request
185
     * @param Plugin $Plugin
186
     *
187
     * @return RedirectResponse
188
     */
189
    public function update(Request $request, Plugin $Plugin)
190
    {
191
        $form = $this->formFactory
192
            ->createNamedBuilder(
193
                'form'.$Plugin->getId(),
194
                PluginManagementType::class,
195
                null,
196
                [
197
                    'plugin_id' => null, // placeHolder
198
                ]
199
            )
200
            ->getForm();
201
202
        $message = '';
203
        $form->handleRequest($request);
204
        if ($form->isSubmitted() && $form->isValid()) {
205
            $tmpDir = null;
206
            try {
207
                $formFile = $form['plugin_archive']->getData();
208
                $tmpDir = $this->pluginService->createTempDir();
209
                $tmpFile = sha1(StringUtil::random(32)).'.'.$formFile->getClientOriginalExtension();
210
                $formFile->move($tmpDir, $tmpFile);
211
                $this->pluginService->update($Plugin, $tmpDir.'/'.$tmpFile);
212
                $fs = new Filesystem();
213
                $fs->remove($tmpDir);
214
                $this->addSuccess('admin.plugin.update.complete', 'admin');
215
216
                return $this->redirectToRoute('admin_store_plugin');
217
            } catch (PluginException $e) {
218
                if (!empty($tmpDir) && file_exists($tmpDir)) {
219
                    $fs = new Filesystem();
220
                    $fs->remove($tmpDir);
221
                }
222
                $message = $e->getMessage();
223
            } catch (\Exception $er) {
224
                // Catch composer install error | Other error
225
                if (!empty($tmpDir) && file_exists($tmpDir)) {
226
                    $fs = new Filesystem();
227
                    $fs->remove($tmpDir);
228
                }
229
                log_error('plugin install failed.', ['original-message' => $er->getMessage()]);
230
                $message = 'admin.plugin.install.fail';
231
            }
232
        } else {
233
            $errors = $form->getErrors(true);
234
            foreach ($errors as $error) {
235
                $message = $error->getMessage();
0 ignored issues
show
Bug introduced by
The method getMessage does only exist in Symfony\Component\Form\FormError, but not in Symfony\Component\Form\FormErrorIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
236
            }
237
        }
238
239
        $this->addError($message, 'admin');
240
241
        return $this->redirectToRoute('admin_store_plugin');
242
    }
243
244
    /**
245
     * 対象のプラグインを有効にします。
246
     *
247
     * @Route("/%eccube_admin_route%/store/plugin/{id}/enable", requirements={"id" = "\d+"}, name="admin_store_plugin_enable", methods={"POST"})
248
     *
249
     * @param Plugin $Plugin
250
     *
251
     * @return RedirectResponse|JsonResponse
252
     *
253
     * @throws PluginException
254
     */
255
    public function enable(Plugin $Plugin, CacheUtil $cacheUtil, Request $request)
256
    {
257
        $this->isTokenValid();
258
259
        $log = null;
260
261
        if ($Plugin->isEnabled()) {
262
            if ($request->isXmlHttpRequest()) {
263
                return $this->json(['success' => true]);
264
            } else {
265
                $this->addError('admin.plugin.already.enable', 'admin');
266
            }
267
        } else {
268
            // ストアからインストールしたプラグインは依存プラグインが有効化されているかを確認
269
            if ($Plugin->getSource()) {
270
                $requires = $this->pluginService->getPluginRequired($Plugin);
271 View Code Duplication
                $requires = array_filter($requires, function ($req) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
272
                    $code = preg_replace('/^ec-cube\//', '', $req['name']);
273
                    /** @var Plugin $DependPlugin */
274
                    $DependPlugin = $this->pluginRepository->findOneBy(['code' => $code]);
275
276
                    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...
277
                });
278
                if (!empty($requires)) {
279
                    $names = array_map(function ($req) {
280
                        return "「${req['description']}」";
281
                    }, $requires);
282
                    $message = trans('%depend_name%を先に有効化してください。', ['%name%' => $Plugin->getName(), '%depend_name%' => implode(', ', $names)]);
283
284
                    if ($request->isXmlHttpRequest()) {
285
                        return $this->json(['success' => false, 'message' => $message], 400);
286
                    } else {
287
                        $this->addError($message, 'admin');
288
289
                        return $this->redirectToRoute('admin_store_plugin');
290
                    }
291
                }
292
            }
293
294
            ob_start();
295
296
            if (!$Plugin->isInitialized()) {
297
                $this->pluginService->installWithCode($Plugin->getCode());
298
            }
299
300
            $this->pluginService->enable($Plugin);
301
            $log = ob_get_clean();
302
            ob_end_flush();
303
        }
304
305
        $cacheUtil->clearCache();
306
307 View Code Duplication
        if ($request->isXmlHttpRequest()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
308
            return $this->json(['success' => true, 'log' => $log]);
309
        } else {
310
            $this->addSuccess(trans('「%plugin_name%」を有効にしました。', ['%plugin_name%' => $Plugin->getName()]), 'admin');
311
312
            return $this->redirectToRoute('admin_store_plugin');
313
        }
314
    }
315
316
    /**
317
     * 対象のプラグインを無効にします。
318
     *
319
     * @Route("/%eccube_admin_route%/store/plugin/{id}/disable", requirements={"id" = "\d+"}, name="admin_store_plugin_disable", methods={"POST"})
320
     *
321
     * @param Request $request
322
     * @param Plugin $Plugin
323
     * @param CacheUtil $cacheUtil
324
     *
325
     * @return \Symfony\Component\HttpFoundation\JsonResponse|RedirectResponse
326
     */
327
    public function disable(Request $request, Plugin $Plugin, CacheUtil $cacheUtil)
328
    {
329
        $this->isTokenValid();
330
331
        $log = null;
332
        if ($Plugin->isEnabled()) {
333
            $dependents = $this->pluginService->findDependentPluginNeedDisable($Plugin->getCode());
334
            if (!empty($dependents)) {
335
                $dependName = $dependents[0];
336
                $DependPlugin = $this->pluginRepository->findOneBy(['code' => $dependents[0]]);
337
                if ($DependPlugin) {
338
                    $dependName = $DependPlugin->getName();
339
                }
340
                $message = trans('admin.plugin.disable.depend', ['%name%' => $Plugin->getName(), '%depend_name%' => $dependName]);
341
342
                if ($request->isXmlHttpRequest()) {
343
                    return $this->json(['message' => $message], 400);
344
                } else {
345
                    $this->addError($message, 'admin');
346
347
                    return $this->redirectToRoute('admin_store_plugin');
348
                }
349
            }
350
351
            ob_start();
352
            $this->pluginService->disable($Plugin);
353
            $log = ob_get_clean();
354
            ob_end_flush();
355 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
356
            if ($request->isXmlHttpRequest()) {
357
                return $this->json(['success' => true, 'log' => $log]);
358
            } else {
359
                $this->addError('admin.plugin.already.disable', 'admin');
360
361
                return $this->redirectToRoute('admin_store_plugin');
362
            }
363
        }
364
365
        $cacheUtil->clearCache();
366
367 View Code Duplication
        if ($request->isXmlHttpRequest()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
            return $this->json(['success' => true, 'log' => $log]);
369
        } else {
370
            $this->addSuccess('admin.plugin.disable.complete', 'admin');
371
372
            return $this->redirectToRoute('admin_store_plugin');
373
        }
374
    }
375
376
    /**
377
     * 対象のプラグインを削除します。
378
     *
379
     * @Route("/%eccube_admin_route%/store/plugin/{id}/uninstall", requirements={"id" = "\d+"}, name="admin_store_plugin_uninstall", methods={"DELETE"})
380
     *
381
     * @param Plugin $Plugin
382
     *
383
     * @return RedirectResponse
384
     *
385
     * @throws \Exception
386
     */
387
    public function uninstall(Plugin $Plugin)
388
    {
389
        $this->isTokenValid();
390
391
        if ($Plugin->isEnabled()) {
392
            $this->addError('admin.plugin.uninstall.error.not_disable', 'admin');
393
394
            return $this->redirectToRoute('admin_store_plugin');
395
        }
396
397
        // Check other plugin depend on it
398
        $pluginCode = $Plugin->getCode();
399
        $otherDepend = $this->pluginService->findDependentPlugin($pluginCode);
400
        if (!empty($otherDepend)) {
401
            $DependPlugin = $this->pluginRepository->findOneBy(['code' => $otherDepend[0]]);
402
            $dependName = $otherDepend[0];
403
            if ($DependPlugin) {
404
                $dependName = $DependPlugin->getName();
405
            }
406
            $message = trans('admin.plugin.uninstall.depend', ['%name%' => $Plugin->getName(), '%depend_name%' => $dependName]);
407
            $this->addError($message, 'admin');
408
409
            return $this->redirectToRoute('admin_store_plugin');
410
        }
411
412
        $this->pluginService->uninstall($Plugin);
413
        $this->addSuccess('admin.plugin.uninstall.complete', 'admin');
414
415
        return $this->redirectToRoute('admin_store_plugin');
416
    }
417
418
    /**
419
     * プラグインファイルアップロード画面
420
     *
421
     * @Route("/%eccube_admin_route%/store/plugin/install", name="admin_store_plugin_install")
422
     * @Template("@admin/Store/plugin_install.twig")
423
     *
424
     * @param Request $request
425
     *
426
     * @return array|RedirectResponse
427
     */
428
    public function install(Request $request)
429
    {
430
        $form = $this->formFactory
431
            ->createBuilder(PluginLocalInstallType::class)
432
            ->getForm();
433
        $errors = [];
434
        $form->handleRequest($request);
435
        if ($form->isSubmitted() && $form->isValid()) {
436
            $tmpDir = null;
437
            try {
438
                $service = $this->pluginService;
439
                /** @var UploadedFile $formFile */
440
                $formFile = $form['plugin_archive']->getData();
441
                $tmpDir = $service->createTempDir();
442
                // 拡張子を付けないとpharが動かないので付ける
443
                $tmpFile = sha1(StringUtil::random(32)).'.'.$formFile->getClientOriginalExtension();
444
                $formFile->move($tmpDir, $tmpFile);
445
                $tmpPath = $tmpDir.'/'.$tmpFile;
446
                $service->install($tmpPath);
447
                // Remove tmp file
448
                $fs = new Filesystem();
449
                $fs->remove($tmpDir);
450
                $this->addSuccess('admin.plugin.install.complete', 'admin');
451
452
                return $this->redirectToRoute('admin_store_plugin');
453
            } catch (PluginException $e) {
454
                if (!empty($tmpDir) && file_exists($tmpDir)) {
455
                    $fs = new Filesystem();
456
                    $fs->remove($tmpDir);
457
                }
458
                log_error('plugin install failed.', ['original-message' => $e->getMessage()]);
459
                $errors[] = $e;
460
            } catch (\Exception $er) {
461
                // Catch composer install error | Other error
462
                if (!empty($tmpDir) && file_exists($tmpDir)) {
463
                    $fs = new Filesystem();
464
                    $fs->remove($tmpDir);
465
                }
466
                log_error('plugin install failed.', ['original-message' => $er->getMessage()]);
467
                $this->addError('admin.plugin.install.fail', 'admin');
468
            }
469
        } else {
470
            foreach ($form->getErrors(true) as $error) {
471
                $errors[] = $error;
472
            }
473
        }
474
475
        return [
476
            'form' => $form->createView(),
477
            'errors' => $errors,
478
        ];
479
    }
480
481
    /**
482
     * 認証キー設定画面
483
     *
484
     * @Route("/%eccube_admin_route%/store/plugin/authentication_setting", name="admin_store_authentication_setting")
485
     * @Template("@admin/Store/authentication_setting.twig")
486
     */
487
    public function authenticationSetting(Request $request)
488
    {
489
        $builder = $this->formFactory
490
            ->createBuilder(AuthenticationType::class, $this->BaseInfo);
491
492
        $form = $builder->getForm();
493
        $form->handleRequest($request);
494
495
        if ($form->isSubmitted() && $form->isValid()) {
496
            // 認証キーの登録 and PHP path
497
            $this->BaseInfo = $form->getData();
498
            $this->entityManager->persist($this->BaseInfo);
499
            $this->entityManager->flush();
500
501
            $this->addSuccess('admin.common.save_complete', 'admin');
502
        }
503
504
        return [
505
            'form' => $form->createView(),
506
            'eccubeUrl' => $this->generateUrl('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL),
507
            'eccubeShopName' => $this->BaseInfo->getShopName(),
508
        ];
509
    }
510
511
    /**
512
     * フォルダ設置のみのプラグインを取得する.
513
     *
514
     * @param array $plugins
515
     *
516
     * @return array
517
     *
518
     * @throws PluginException
519
     */
520
    protected function getUnregisteredPlugins(array $plugins)
521
    {
522
        $finder = new Finder();
523
        $pluginCodes = [];
524
525
        // DB登録済みプラグインコードのみ取得
526
        foreach ($plugins as $key => $plugin) {
527
            $pluginCodes[] = $plugin->getCode();
528
        }
529
        // DB登録済みプラグインコードPluginディレクトリから排他
530
        $dirs = $finder->in($this->eccubeConfig['plugin_realdir'])->depth(0)->directories();
531
532
        // プラグイン基本チェック
533
        $unregisteredPlugins = [];
534
        foreach ($dirs as $dir) {
535
            $pluginCode = $dir->getBasename();
536
            if (in_array($pluginCode, $pluginCodes, true)) {
537
                continue;
538
            }
539
            try {
540
                $this->pluginService->checkPluginArchiveContent($dir->getRealPath());
541
            } catch (PluginException $e) {
542
                //config.yamlに不備があった際は全てスキップ
543
                log_warning($e->getMessage());
544
                continue;
545
            }
546
            $config = $this->pluginService->readConfig($dir->getRealPath());
547
            $unregisteredPlugins[$pluginCode]['name'] = isset($config['name']) ? $config['name'] : null;
548
            $unregisteredPlugins[$pluginCode]['event'] = isset($config['event']) ? $config['event'] : null;
549
            $unregisteredPlugins[$pluginCode]['version'] = isset($config['version']) ? $config['version'] : null;
550
            $unregisteredPlugins[$pluginCode]['enabled'] = Constant::DISABLED;
551
            $unregisteredPlugins[$pluginCode]['code'] = isset($config['code']) ? $config['code'] : null;
552
        }
553
554
        return $unregisteredPlugins;
555
    }
556
}
557