Failed Conditions
Push — sf/package-api ( 831849 )
by Kiyotaka
05:53
created

OwnerStoreController::apiInstall()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 3
nop 1
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
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\Master\PageMax;
20
use Eccube\Entity\Plugin;
21
use Eccube\Form\Type\Admin\SearchPluginApiType;
22
use Eccube\Repository\BaseInfoRepository;
23
use Eccube\Repository\PluginRepository;
24
use Eccube\Service\Composer\ComposerApiService;
25
use Eccube\Service\Composer\ComposerProcessService;
26
use Eccube\Service\Composer\ComposerServiceInterface;
27
use Eccube\Service\PluginApiService;
28
use Eccube\Service\PluginService;
29
use Eccube\Service\SystemService;
30
use Eccube\Util\FormUtil;
31
use Knp\Component\Pager\Paginator;
32
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
33
use Symfony\Component\HttpFoundation\RedirectResponse;
34
use Symfony\Component\HttpFoundation\Request;
35
use Symfony\Component\HttpFoundation\Response;
36
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
37
use Symfony\Component\Routing\Annotation\Route;
38
39
/**
40
 * @Route("/%eccube_admin_route%/store/plugin/api")
41
 */
42
class OwnerStoreController extends AbstractController
43
{
44
    /**
45
     * @var PluginRepository
46
     */
47
    protected $pluginRepository;
48
49
    /**
50
     * @var PluginService
51
     */
52
    protected $pluginService;
53
54
    /**
55
     * @var ComposerServiceInterface
56
     */
57
    protected $composerService;
58
59
    /**
60
     * @var SystemService
61
     */
62
    protected $systemService;
63
64
    /**
65
     * @var PluginApiService
66
     */
67
    protected $pluginApiService;
68
69
    private static $vendorName = 'ec-cube';
70
71
    /** @var BaseInfo */
72
    private $BaseInfo;
73
74
    /**
75
     * OwnerStoreController constructor.
76
     *
77
     * @param PluginRepository $pluginRepository
78
     * @param PluginService $pluginService
79
     * @param ComposerProcessService $composerProcessService
80
     * @param ComposerApiService $composerApiService
81
     * @param SystemService $systemService
82
     * @param PluginApiService $pluginApiService
83
     * @param BaseInfoRepository $baseInfoRepository
84
     * @throws \Doctrine\ORM\NoResultException
85
     * @throws \Doctrine\ORM\NonUniqueResultException
86
     */
87
    public function __construct(
88
        PluginRepository $pluginRepository,
89
        PluginService $pluginService,
90
        ComposerProcessService $composerProcessService,
91
        ComposerApiService $composerApiService,
92
        SystemService $systemService,
93
        PluginApiService $pluginApiService,
94
        BaseInfoRepository $baseInfoRepository
95
    ) {
96
        $this->pluginRepository = $pluginRepository;
97
        $this->pluginService = $pluginService;
98
        $this->systemService = $systemService;
99
        $this->pluginApiService = $pluginApiService;
100
        $this->BaseInfo = $baseInfoRepository->get();
101
102
        // TODO: Check the flow of the composer service below
103
        $memoryLimit = $this->systemService->getMemoryLimit();
104
        if ($memoryLimit == -1 or $memoryLimit >= $this->eccubeConfig['eccube_composer_memory_limit']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
105
            $this->composerService = $composerApiService;
106
        } else {
107
            $this->composerService = $composerProcessService;
108
        }
109
    }
110
111
    /**
112
     * Owner's Store Plugin Installation Screen - Search function
113
     *
114
     * @Route("/search", name="admin_store_plugin_owners_search")
115
     * @Route("/search/page/{page_no}", name="admin_store_plugin_owners_search_page", requirements={"page_no" = "\d+"})
116
     * @Template("@admin/Store/plugin_search.twig")
117
     *
118
     * @param Request     $request
119
     * @param int $page_no
120
     * @param Paginator $paginator
121
     *
122
     * @return array
123
     */
124
    public function search(Request $request, $page_no = null, Paginator $paginator)
125
    {
126
127
        if (!$this->BaseInfo->getAuthenticationKey()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->BaseInfo->getAuthenticationKey() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
128
            $this->addWarning('認証キーを設定してください。', 'admin');
129
            return $this->redirectToRoute('admin_store_authentication_setting');
130
        }
131
132
        // Acquire downloadable plug-in information from owners store
133
        $items = [];
134
        $message = '';
135
        $total = 0;
136
        $category = [];
137
138
        list($json, $info) = $this->pluginApiService->getCategory();
139
        if (!empty($json)) {
140
            $data = json_decode($json, true);
141
            $category = array_column($data, 'name', 'id');
142
        }
143
144
        // build form with master data
145
        $builder = $this->formFactory
146
            ->createBuilder(SearchPluginApiType::class, null, ['category' => $category]);
147
        $searchForm = $builder->getForm();
148
149
        $searchForm->handleRequest($request);
150
        $searchData = $searchForm->getData();
151
        if ($searchForm->isSubmitted()) {
152
            if ($searchForm->isValid()) {
153
                $page_no = 1;
154
                $searchData = $searchForm->getData();
155
                $this->session->set('eccube.admin.plugin_api.search', FormUtil::getViewData($searchForm));
156
                $this->session->set('eccube.admin.plugin_api.search.page_no', $page_no);
157
            }
158
        } else {
159
            // quick search
160
            if (is_numeric($categoryId = $request->get('category_id')) && array_key_exists($categoryId, $category)) {
161
                $searchForm['category_id']->setData($categoryId);
162
            }
163
            // reset page count
164
            $this->session->set('eccube.admin.plugin_api.search.page_count', $this->eccubeConfig->get('eccube_default_page_count'));
165 View Code Duplication
            if (null !== $page_no || $request->get('resume')) {
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...
166
                if ($page_no) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $page_no of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
167
                    $this->session->set('eccube.admin.plugin_api.search.page_no', (int) $page_no);
168
                } else {
169
                    $page_no = $this->session->get('eccube.admin.plugin_api.search.page_no', 1);
170
                }
171
                $viewData = $this->session->get('eccube.admin.plugin_api.search', []);
172
                $searchData = FormUtil::submitAndGetData($searchForm, $viewData);
173
            } else {
174
                $page_no = 1;
175
                // submit default value
176
                $viewData = FormUtil::getViewData($searchForm);
177
                $searchData = FormUtil::submitAndGetData($searchForm, $viewData);
178
                $this->session->set('eccube.admin.plugin_api.search', $searchData);
179
                $this->session->set('eccube.admin.plugin_api.search.page_no', $page_no);
180
            }
181
        }
182
183
        // set page count
184
        $pageCount = $this->session->get('eccube.admin.plugin_api.search.page_count', $this->eccubeConfig->get('eccube_default_page_count'));
185
        if (($PageMax = $searchForm['page_count']->getData()) instanceof PageMax) {
186
            $pageCount = $PageMax->getId();
187
            $this->session->set('eccube.admin.plugin_api.search.page_count', $pageCount);
188
        }
189
190
        // Owner's store communication
191
        $searchData['page_no'] = $page_no;
192
        $searchData['page_count'] = $pageCount;
193
        list($json, $info) = $this->pluginApiService->getPlugins($searchData);
194
        if (empty($json)) {
195
            $message = $this->pluginApiService->getResponseErrorMessage($info);
196
        } else {
197
            $data = json_decode($json, true);
198
            $total = $data['total'];
199
            if (isset($data['plugins']) && count($data['plugins']) > 0) {
200
                // Check plugin installed
201
                $pluginInstalled = $this->pluginRepository->findAll();
202
                // Update_status 1 : not install/purchased 、2 : Installed、 3 : Update、4 : not purchased
203
                foreach ($data['plugins'] as $item) {
204
                    // Not install/purchased
205
                    $item['update_status'] = 1;
206
                    /** @var Plugin $plugin */
207
                    foreach ($pluginInstalled as $plugin) {
208
                        if ($plugin->getSource() == $item['id']) {
209
                            // Installed
210
                            $item['update_status'] = 2;
211
                            if ($this->pluginService->isUpdate($plugin->getVersion(), $item['version'])) {
212
                                // Need update
213
                                $item['update_status'] = 3;
214
                            }
215
                        }
216
                    }
217
                    if ($item['purchased'] == false && (isset($item['purchase_required']) && $item['purchase_required'] == true)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $item['purchase_required'] of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
218
                        // Not purchased with paid items
219
                        $item['update_status'] = 4;
220
                    }
221
222
                    $item = $this->pluginService->buildInfo($item);
223
                    $items[] = $item;
224
                }
225
226
                // Todo: news api will remove this?
227
                // Promotion item
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
228
//                $i = 0;
229
//                foreach ($items as $item) {
230
//                    if ($item['promotion'] == 1) {
231
//                        $promotionItems[] = $item;
232
//                        unset($items[$i]);
233
//                    }
234
//                    $i++;
235
//                }
236
            } else {
237
                $message = trans('ownerstore.text.error.ec_cube_error');
238
            }
239
        }
240
241
        // The usage is set because `$items` are already paged.
242
        // virtual paging
243
        $pagination = $paginator->paginate($items, 1, $pageCount);
244
        $pagination->setTotalItemCount($total);
245
        $pagination->setCurrentPageNumber($page_no);
246
        $pagination->setItemNumberPerPage($pageCount);
247
248
        return [
249
            'pagination' => $pagination,
250
            'total' => $total,
251
            'searchForm' => $searchForm->createView(),
252
            'page_no' => $page_no,
253
            'message' => $message,
254
            'Categories' => $category,
255
        ];
256
    }
257
258
    /**
259
     * Do confirm page
260
     *
261
     * @Route("/install/{id}/confirm", requirements={"id" = "\d+"}, name="admin_store_plugin_install_confirm")
262
     * @Template("@admin/Store/plugin_confirm.twig")
263
     *
264
     * @param Request $request
265
     * @param string $id
266
     *
267
     * @return array
268
     * @throws \Eccube\Exception\PluginException
269
     */
270
    public function doConfirm(Request $request, $id)
271
    {
272
        list($json) = $this->pluginApiService->getPlugin($id);
273
        $plugin = [];
274
        if ($json) {
275
            $data = json_decode($json, true);
276
            $plugin = $this->pluginService->buildInfo($data);
277
        }
278
279
        if (empty($plugin)) {
280
            throw new NotFoundHttpException();
281
        }
282
283
        // Todo: need define plugin's dependency mechanism
284
        $requires = $this->pluginService->getPluginRequired($plugin);
285
286
        return [
287
            'item' => $plugin,
288
            'requires' => $requires,
289
            'is_update' => $request->get('is_update', false),
290
        ];
291
    }
292
293
    /**
294
     * Api Install plugin by composer connect with package repo
295
     *
296
     * @Route("/install", name="admin_store_plugin_api_install", methods={"POST"})
297
     *
298
     * @param Request $request
299
     *
300
     * @return \Symfony\Component\HttpFoundation\JsonResponse
301
     */
302
    public function apiInstall(Request $request)
303
    {
304
        $this->isTokenValid();
305
306
        $pluginCode = $request->get('pluginCode');
307
308
        $log = null;
309
        try {
310
            $log = $this->composerService->execRequire("ec-cube/".$pluginCode);
311
312
            return $this->json(['success' => true, 'log' => $log]);
313
        } catch (\Exception $e) {
314
            $log = $e->getMessage();
315
            log_error($e);
316
        }
317
318
        return $this->json(['success' => false, 'log' => $log], 500);
319
    }
320
321
    /**
322
     * Do confirm page
323
     *
324
     * @Route("/delete/{id}/confirm", requirements={"id" = "\d+"}, name="admin_store_plugin_delete_confirm")
325
     * @Template("@admin/Store/plugin_confirm_uninstall.twig")
326
     *
327
     * @param Plugin $Plugin
328
     *
329
     * @return array|RedirectResponse
330
     */
331
    public function deleteConfirm(Plugin $Plugin)
332
    {
333
        // Owner's store communication
334
        $url = $this->eccubeConfig['eccube_package_repo_url'].'/search/packages.json';
335
        list($json, $info) = $this->getRequestApi($url);
0 ignored issues
show
Unused Code introduced by
The assignment to $info is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Deprecated Code introduced by
The method Eccube\Controller\Admin\...roller::getRequestApi() has been deprecated with message: since release, please preference PluginApiService

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
336
        $data = json_decode($json, true);
337
        $items = $data['item'];
338
339
        // The plugin depends on it
340
        $pluginCode = $Plugin->getCode();
341
        $otherDepend = $this->pluginService->findDependentPlugin($pluginCode);
342
343
        if (!empty($otherDepend)) {
344
            $DependPlugin = $this->pluginRepository->findOneBy(['code' => $otherDepend[0]]);
345
            $dependName = $otherDepend[0];
346
            if ($DependPlugin) {
347
                $dependName = $DependPlugin->getName();
348
            }
349
            $message = trans('admin.plugin.uninstall.depend', ['%name%' => $Plugin->getName(), '%depend_name%' => $dependName]);
350
            $this->addError($message, 'admin');
351
352
            return $this->redirectToRoute('admin_store_plugin');
353
        }
354
355
        // Check plugin in api
356
        $pluginSource = $Plugin->getSource();
357
        $index = array_search($pluginSource, array_column($items, 'product_id'));
358
        if ($index === false) {
359
            throw new NotFoundHttpException();
360
        }
361
362
        // Build info
363
        $pluginCode = $Plugin->getCode();
364
        $plugin = $this->pluginService->buildInfo($items, $pluginCode);
0 ignored issues
show
Unused Code introduced by
The call to PluginService::buildInfo() has too many arguments starting with $pluginCode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
365
        $plugin['id'] = $Plugin->getId();
366
367
        return [
368
            'item' => $plugin,
369
        ];
370
    }
371
372
    /**
373
     * New ways to remove plugin: using composer command
374
     *
375
     * @Route("/delete/{id}/uninstall", requirements={"id" = "\d+"}, name="admin_store_plugin_api_uninstall", methods={"DELETE"})
376
     *
377
     * @param Plugin $Plugin
378
     *
379
     * @return \Symfony\Component\HttpFoundation\JsonResponse
380
     */
381
    public function apiUninstall(Plugin $Plugin)
382
    {
383
        $this->isTokenValid();
384
385
        if ($Plugin->isEnabled()) {
386
            return $this->json(['success' => false, 'message' => trans('admin.plugin.uninstall.error.not_disable')], 400);
387
        }
388
389
        $pluginCode = $Plugin->getCode();
390
        $otherDepend = $this->pluginService->findDependentPlugin($pluginCode);
391
392
        if (!empty($otherDepend)) {
393
            $DependPlugin = $this->pluginRepository->findOneBy(['code' => $otherDepend[0]]);
394
            $dependName = $otherDepend[0];
395
            if ($DependPlugin) {
396
                $dependName = $DependPlugin->getName();
397
            }
398
            $message = trans('admin.plugin.uninstall.depend', ['%name%' => $Plugin->getName(), '%depend_name%' => $dependName]);
399
400
            return $this->json(['success' => false, 'message' => $message], 400);
401
        }
402
403
        $pluginCode = $Plugin->getCode();
404
        $packageName = self::$vendorName.'/'.$pluginCode;
405
        try {
406
            $log = $this->composerService->execRemove($packageName);
407
            return $this->json(['success' => false, 'log' => $log]);
408
        } catch (\Exception $e) {
409
            log_error($e);
410
            return $this->json(['success' => false, 'log' => $e->getMessage()], 500);
411
        }
412
413
    }
414
415
    /**
416
     * オーナーズブラグインインストール、アップデート
417
     *
418
     * @Route("/upgrade/{pluginCode}/{version}", name="admin_store_plugin_api_upgrade", methods={"PUT"})
419
     *
420
     * @param string $pluginCode
421
     * @param string $version
422
     *
423
     * @return RedirectResponse
424
     */
425
    public function apiUpgrade($pluginCode, $version)
426
    {
427
        $this->isTokenValid();
428
        // Run install plugin
429
        $this->forward($this->generateUrl('admin_store_plugin_api_install', ['pluginCode' => $pluginCode, 'eccubeVersion' => Constant::VERSION, 'version' => $version]));
430
431
        if ($this->session->getFlashBag()->has('eccube.admin.error')) {
432
            $this->session->getFlashBag()->clear();
433
            $this->addError('admin.plugin.update.error', 'admin');
434
435
            return $this->redirectToRoute('admin_store_plugin');
436
        }
437
        $this->session->getFlashBag()->clear();
438
        $this->addSuccess('admin.plugin.update.complete', 'admin');
439
440
        return $this->redirectToRoute('admin_store_plugin');
441
    }
442
443
    /**
444
     * Do confirm update page
445
     *
446
     * @Route("/upgrade/{id}/confirm", requirements={"id" = "\d+"}, name="admin_store_plugin_update_confirm")
447
     * @Template("@admin/Store/plugin_confirm.twig")
448
     *
449
     * @param Plugin $plugin
450
     *
451
     * @return Response
452
     */
453
    public function doUpdateConfirm(Plugin $plugin)
454
    {
455
        $source = $plugin->getSource();
456
457
        return $this->forwardToRoute('admin_store_plugin_install_confirm', ['id' => $source, 'is_update' => true]);
458
    }
459
460
    /**
461
     * API request processing
462
     *
463
     * @param string $url
464
     *
465
     * @return array
466
     *
467
     * @deprecated since release, please preference PluginApiService
468
     */
469
    private function getRequestApi($url)
470
    {
471
        $curl = curl_init($url);
472
473
        // Option array
474
        $options = [
475
            // HEADER
476
            CURLOPT_HTTPGET => true,
477
            CURLOPT_SSL_VERIFYPEER => false,
478
            CURLOPT_RETURNTRANSFER => true,
479
            CURLOPT_FAILONERROR => true,
480
            CURLOPT_CAINFO => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(),
481
        ];
482
483
        // Set option value
484
        curl_setopt_array($curl, $options);
485
        $result = curl_exec($curl);
486
        $info = curl_getinfo($curl);
487
        $message = curl_error($curl);
488
        $info['message'] = $message;
489
        curl_close($curl);
490
491
        log_info('http get_info', $info);
492
493
        return [$result, $info];
494
    }
495
496
    /**
497
     * API post request processing
498
     *
499
     * @param string $url
500
     * @param array $data
501
     *
502
     * @return array
503
     *
504
     * @deprecated since release, please preference PluginApiService
505
     */
506
    private function postRequestApi($url, $data)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
507
    {
508
        $curl = curl_init($url);
509
        curl_setopt($curl, CURLOPT_URL, $url);
510
        curl_setopt($curl, CURLOPT_POST, 1);
511
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
512
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
513
        $result = curl_exec($curl);
514
        $info = curl_getinfo($curl);
515
        $message = curl_error($curl);
516
        $info['message'] = $message;
517
        curl_close($curl);
518
        log_info('http post_info', $info);
519
520
        return [$result, $info];
521
    }
522
}
523