Completed
Push — experimental/3.1 ( f5947c...5d0f05 )
by Ryo
48:59
created

OwnerStoreController::apiUpgrade()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 11
c 1
b 0
f 1
nc 2
nop 3
dl 0
loc 19
ccs 0
cts 0
cp 0
crap 6
rs 9.4285
1
<?php
2
/*
3
 * This file is part of EC-CUBE
4
 *
5
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
6
 *
7
 * http://www.lockon.co.jp/
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation; either version 2
12
 * of the License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
 */
23
namespace Eccube\Controller\Admin\Store;
24
25
use Doctrine\ORM\EntityManager;
26
use Eccube\Annotation\Inject;
27
use Eccube\Application;
28
use Eccube\Common\Constant;
29
use Eccube\Controller\AbstractController;
30
use Eccube\Entity\Plugin;
31
use Eccube\Repository\PluginRepository;
32
use Eccube\Service\Composer\ComposerServiceInterface;
33
use Eccube\Service\PluginService;
34
use Eccube\Service\SystemService;
35
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
36
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
37
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
38
use Symfony\Component\HttpFoundation\RedirectResponse;
39
use Symfony\Component\HttpFoundation\Request;
40
use Symfony\Component\HttpFoundation\Response;
41
use Symfony\Component\HttpFoundation\Session\Session;
42
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
43
44
/**
45
 * @Route("/{_admin}/store/plugin/api", service=OwnerStoreController::class)
46
 */
47
class OwnerStoreController extends AbstractController
48
{
49
    /**
50
     * @Inject("config")
51
     * @var array
52
     */
53
    protected $appConfig;
54
55
    /**
56
     * @Inject(PluginRepository::class)
57
     * @var PluginRepository
58
     */
59
    protected $pluginRepository;
60
61
    /**
62
     * @Inject(PluginService::class)
63
     * @var PluginService
64
     */
65
    protected $pluginService;
66
67
    /**
68
     * @Inject("eccube.service.composer")
69
     * @var ComposerServiceInterface
70
     */
71
    protected $composerService;
72
73
    /**
74
     * @var EntityManager
75
     * @Inject("orm.em")
76
     */
77
    protected $em;
78
79
    /**
80
     * @Inject(SystemService::class)
81
     * @var SystemService
82
     */
83
    protected $systemService;
84
85
    private static $vendorName = 'ec-cube';
86
87
    /**
88
     * Owner's Store Plugin Installation Screen - Search function
89
     *
90
     * @Route("/search", name="admin_store_plugin_owners_search")
91
     * @Template("Store/plugin_search.twig")
92
     * @param Application $app
93
     * @param Request     $request
94
     * @return array
95
     */
96
    public function search(Application $app, Request $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
97
    {
98
        // Acquire downloadable plug-in information from owners store
99
        $items = array();
100
        $promotionItems = array();
101
        $message = '';
102
        // Owner's store communication
103
        $url = $this->appConfig['package_repo_url'].'/search/packages.json';
104
        list($json, $info) = $this->getRequestApi($url);
105
        if ($json === false) {
106
            $message = $this->getResponseErrorMessage($info);
107
        } else {
108
            $data = json_decode($json, true);
109
            if (isset($data['success']) && $data['success']) {
110
                // Check plugin installed
111
                $pluginInstalled = $this->pluginRepository->findAll();
112
                // Update_status 1 : not install/purchased 、2 : Installed、 3 : Update、4 : paid purchase
113
                foreach ($data['item'] as $item) {
114
                    // Not install/purchased
115
                    $item['update_status'] = 1;
116
                    /** @var Plugin $plugin */
117
                    foreach ($pluginInstalled as $plugin) {
118
                        if ($plugin->getSource() == $item['product_id']) {
119
                            // Installed
120
                            $item['update_status'] = 2;
121
                            if ($this->pluginService->isUpdate($plugin->getVersion(), $item['version'])) {
122
                                // Need update
123
                                $item['update_status'] = 3;
124
                            }
125
                        }
126
                    }
127
                    $items[] = $item;
128
                }
129
130
                // EC-CUBE version check
131
                foreach ($items as &$item) {
132
                    // Not applicable version
133
                    $item['version_check'] = 0;
134
                    if (in_array(Constant::VERSION, $item['eccube_version'])) {
135
                        // Match version
136
                        $item['version_check'] = 1;
137
                    }
138
                    if ($item['price'] != '0' && $item['purchased'] == '0') {
139
                        // Not purchased with paid items
140
                        $item['update_status'] = 4;
141
                    }
142
                    // Add plugin dependency
143
                    $item['depend'] = $this->pluginService->getRequirePluginName($items, $item);
144
                }
145
                unset($item);
146
147
                // Promotion item
148
                $i = 0;
149
                foreach ($items as $item) {
150
                    if ($item['promotion'] == 1) {
151
                        $promotionItems[] = $item;
152
                        unset($items[$i]);
153
                    }
154
                    $i++;
155
                }
156
            } else {
157
                $message = $app->trans('admin.plugin.authentication.fail');
158
            }
159
        }
160
161
        return [
162
            'items' => $items,
163
            'promotionItems' => $promotionItems,
164
            'message' => $message,
165
        ];
166
    }
167
168
    /**
169
     * Do confirm page
170
     *
171
     * @Route("/install/{id}/confirm", requirements={"id" = "\d+"}, name="admin_store_plugin_install_confirm")
172
     * @Template("Store/plugin_confirm.twig")
173
     * @param Application $app
174
     * @param Request     $request
175
     * @param string      $id
176
     * @return array
177
     */
178
    public function doConfirm(Application $app, Request $request, $id)
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
179
    {
180
        // Owner's store communication
181
        $url = $this->appConfig['package_repo_url'].'/search/packages.json';
182
        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...
183
        $data = json_decode($json, true);
184
        $items = $data['item'];
185
186
        // Find plugin in api
187
        $index = array_search($id, array_column($items, 'product_id'));
188
        if ($index === false) {
189
            throw new NotFoundHttpException();
190
        }
191
192
        $pluginCode = $items[$index]['product_code'];
193
194
        $plugin = $this->pluginService->buildInfo($items, $pluginCode);
195
196
        // Prevent infinity loop: A -> B -> A.
197
        $dependents[] = $plugin;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dependents was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dependents = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
198
        $dependents = $this->pluginService->getDependency($items, $plugin, $dependents);
0 ignored issues
show
Bug introduced by
It seems like $plugin defined by $this->pluginService->bu...fo($items, $pluginCode) on line 194 can also be of type null; however, Eccube\Service\PluginService::getDependency() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
199
        // Unset first param
200
        unset($dependents[0]);
201
202
        return [
203
            'item' => $plugin,
204
            'dependents' => $dependents,
205
            'is_update' => $request->get('is_update', false),
206
        ];
207
    }
208
209
    /**
210
     * Api Install plugin by composer connect with package repo
211
     *
212
     * @Route("/install/{pluginCode}/{eccubeVersion}/{version}" , name="admin_store_plugin_api_install")
213
     *
214
     * @param Application $app
215
     * @param Request     $request
216
     * @param string      $pluginCode
217
     * @param string      $eccubeVersion
218
     * @param string      $version
219
     * @return RedirectResponse
220
     */
221
    public function apiInstall(Application $app, Request $request, $pluginCode, $eccubeVersion, $version)
222
    {
223
        // Check plugin code
224
        $url = $this->appConfig['package_repo_url'].'/search/packages.json'.'?eccube_version='.$eccubeVersion.'&plugin_code='.$pluginCode.'&version='.$version;
225
        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...
226
        $existFlg = false;
227
        $data = json_decode($json, true);
228
        if (isset($data['item']) && !empty($data['item'])) {
229
            $existFlg = $this->pluginService->checkPluginExist($data['item'], $pluginCode);
230
        }
231
        if ($existFlg === false) {
232
            log_info(sprintf('%s plugin not found!', $pluginCode));
233
            $app->addError('admin.plugin.not.found', 'admin');
234
235
            return $app->redirect($app->url('admin_store_plugin_owners_search'));
236
        }
237
238
        $items = $data['item'];
239
        $plugin = $this->pluginService->buildInfo($items, $pluginCode);
240
        $dependents[] = $plugin;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dependents was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dependents = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
241
        $dependents = $this->pluginService->getDependency($items, $plugin, $dependents);
0 ignored issues
show
Bug introduced by
It seems like $plugin defined by $this->pluginService->bu...fo($items, $pluginCode) on line 239 can also be of type null; however, Eccube\Service\PluginService::getDependency() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
242
        // Unset first param
243
        unset($dependents[0]);
244
        $dependentModifier = [];
245
        $packageNames = '';
246
        if (!empty($dependents)) {
247
            foreach ($dependents as $key => $item) {
248
                $pluginItem = [
249
                    "product_code" => $item['product_code'],
250
                    "version" => $item['version'],
251
                ];
252
                array_push($dependentModifier, $pluginItem);
253
                // Re-format plugin code
254
                $dependents[$key]['product_code'] = self::$vendorName.'/'.$item['product_code'];
255
            }
256
            $packages = array_column($dependents, 'version', 'product_code');
257
            $packageNames = $this->pluginService->parseToComposerCommand($packages);
258
        }
259
        $packageNames .= ' '.self::$vendorName.'/'.$pluginCode.':'.$version;
260
        $data = array(
261
            'code' => $pluginCode,
262
            'version' => $version,
263
            'core_version' => $eccubeVersion,
264
            'php_version' => phpversion(),
265
            'db_version' => $this->systemService->getDbversion(),
266
            'os' => php_uname('s').' '.php_uname('r').' '.php_uname('v'),
267
            'host' => $request->getHost(),
268
            'web_server' => $request->server->get("SERVER_SOFTWARE"),
269
            'composer_version' => $this->composerService->composerVersion(),
270
            'composer_execute_mode' => $this->composerService->getMode(),
271
            'dependents' => json_encode($dependentModifier),
272
        );
273
274
        try {
275
            $this->composerService->execRequire($packageNames);
276
            // Do report to package repo
277
            $url = $this->appConfig['package_repo_url'] . '/report';
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
278
            $this->postRequestApi($url, $data);
279
            $app->addSuccess('admin.plugin.install.complete', 'admin');
280
281
            return $app->redirect($app->url('admin_store_plugin'));
282
        } catch (\Exception $exception) {
283
            log_info($exception);
284
        }
285
286
        // Do report to package repo
287
        $url = $this->appConfig['package_repo_url'] . '/report/fail';
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
288
        $this->postRequestApi($url, $data);
289
        $app->addError('admin.plugin.install.fail', 'admin');
290
291
        return $app->redirect($app->url('admin_store_plugin_owners_search'));
292
    }
293
294
    /**
295
     * Do confirm page
296
     *
297
     * @Route("/delete/{id}/confirm", requirements={"id" = "\d+"}, name="admin_store_plugin_delete_confirm")
298
     * @Template("Store/plugin_confirm_uninstall.twig")
299
     * @param Application $app
300
     * @param Plugin      $Plugin
301
     * @return array|RedirectResponse
302
     */
303
    public function deleteConfirm(Application $app, Plugin $Plugin)
304
    {
305
        // Owner's store communication
306
        $url = $this->appConfig['package_repo_url'].'/search/packages.json';
307
        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...
308
        $data = json_decode($json, true);
309
        $items = $data['item'];
310
311
        // The plugin depends on it
312
        $pluginCode = $Plugin->getCode();
313
        $otherDepend = $this->pluginService->findDependentPlugin($pluginCode);
314
315
        if (!empty($otherDepend)) {
316
            $DependPlugin = $this->pluginRepository->findOneBy(['code' => $otherDepend[0]]);
317
            $dependName = $otherDepend[0];
318
            if ($DependPlugin) {
319
                $dependName = $DependPlugin->getName();
320
            }
321
            $message = $app->trans('admin.plugin.uninstall.depend', ['%name%' => $Plugin->getName(), '%depend_name%' => $dependName]);
322
            $app->addError($message, 'admin');
323
324
            return $app->redirect($app->url('admin_store_plugin'));
325
        }
326
327
        // Check plugin in api
328
        $pluginSource = $Plugin->getSource();
329
        $index = array_search($pluginSource, array_column($items, 'product_id'));
330
        if ($index === false) {
331
            throw new NotFoundHttpException();
332
        }
333
334
        // Build info
335
        $pluginCode = $Plugin->getCode();
336
        $plugin = $this->pluginService->buildInfo($items, $pluginCode);
337
        $plugin['id'] = $Plugin->getId();
338
339
        return [
340
            'item' => $plugin,
341
        ];
342
    }
343
344
    /**
345
     * New ways to remove plugin: using composer command
346
     *
347
     * @Method("DELETE")
348
     * @Route("/delete/{id}/uninstall", requirements={"id" = "\d+"}, name="admin_store_plugin_api_uninstall")
349
     * @param Application $app
350
     * @param Plugin      $Plugin
351
     * @return RedirectResponse
352
     */
353
    public function apiUninstall(Application $app, Plugin $Plugin)
354
    {
355
        $this->isTokenValid($app);
356
357
        if ($Plugin->isEnabled()) {
358
            $this->pluginService->disable($Plugin);
359
        }
360
        $pluginCode = $Plugin->getCode();
361
        $packageName = self::$vendorName.'/'.$pluginCode;
362
        try {
363
            $this->composerService->execRemove($packageName);
364
            $app->addSuccess('admin.plugin.uninstall.complete', 'admin');
365
        } catch (\Exception $exception) {
366
            log_info($exception);
367
            $app->addError('admin.plugin.uninstall.error', 'admin');
368
        }
369
370
        return $app->redirect($app->url('admin_store_plugin'));
371
    }
372
373
    /**
374
     * オーナーズブラグインインストール、アップデート
375
     *
376
     * @Method("PUT")
377
     * @Route("/upgrade/{pluginCode}/{version}", name="admin_store_plugin_api_upgrade")
378
     *
379
     * @param Application $app
380
     * @param string      $pluginCode
381
     * @param string      $version
382
     * @return RedirectResponse
383
     */
384
    public function apiUpgrade(Application $app, $pluginCode, $version)
385
    {
386
        $this->isTokenValid($app);
387
        // Run install plugin
388
        $app->forward($app->url('admin_store_plugin_api_install', ['pluginCode' => $pluginCode, 'eccubeVersion' => Constant::VERSION, 'version' => $version]));
389
390
        /** @var Session $session */
391
        $session = $app['session'];
392
        if ($session->getFlashBag()->has('eccube.admin.error')) {
393
            $session->getFlashBag()->clear();
394
            $app->addError('admin.plugin.update.error', 'admin');
395
396
            return $app->redirect($app->url('admin_store_plugin'));
397
        }
398
        $session->getFlashBag()->clear();
399
        $app->addSuccess('admin.plugin.update.complete', 'admin');
400
401
        return $app->redirect($app->url('admin_store_plugin'));
402
    }
403
404
    /**
405
     * Do confirm update page
406
     *
407
     * @Route("/upgrade/{id}/confirm", requirements={"id" = "\d+"}, name="admin_store_plugin_update_confirm")
408
     * @Template("Store/plugin_confirm.twig")
409
     * @param Application $app
410
     * @param Plugin      $plugin
411
     * @return Response
412
     */
413
    public function doUpdateConfirm(Application $app, Plugin $plugin)
414
    {
415
        $source = $plugin->getSource();
416
        $url = $app->url('admin_store_plugin_install_confirm', ['id' => $source, 'is_update' => true]);
417
418
        return $app->forward($url);
419
    }
420
421
    /**
422
     * API request processing
423
     *
424
     * @param string  $url
425
     * @return array
426
     */
427
    private function getRequestApi($url)
428
    {
429
        $curl = curl_init($url);
430
431
        // Option array
432
        $options = array(
433
            // HEADER
434
            CURLOPT_HTTPGET => true,
435
            CURLOPT_SSL_VERIFYPEER => false,
436
            CURLOPT_RETURNTRANSFER => true,
437
            CURLOPT_FAILONERROR => true,
438
            CURLOPT_CAINFO => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(),
439
        );
440
441
        // Set option value
442
        curl_setopt_array($curl, $options);
443
        $result = curl_exec($curl);
444
        $info = curl_getinfo($curl);
445
        $message = curl_error($curl);
446
        $info['message'] = $message;
447
        curl_close($curl);
448
449
        log_info('http get_info', $info);
450
451
        return array($result, $info);
452
    }
453
454
    /**
455
     * API post request processing
456
     *
457
     * @param string  $url
458
     * @param array $data
459
     * @return array
460
     */
461
    private function postRequestApi($url, $data)
462
    {
463
        $curl = curl_init($url);
464
        curl_setopt($curl, CURLOPT_URL, $url);
465
        curl_setopt($curl, CURLOPT_POST, 1);
466
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
467
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
468
        $result = curl_exec($curl);
469
        $info = curl_getinfo($curl);
470
        $message = curl_error($curl);
471
        $info['message'] = $message;
472
        curl_close($curl);
473
        log_info('http post_info', $info);
474
475
        return array($result, $info);
476
    }
477
478
    /**
479
     * Get message
480
     *
481
     * @param $info
482
     * @return string
483
     */
484 View Code Duplication
    private function getResponseErrorMessage($info)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
485
    {
486
        if (!empty($info)) {
487
            $statusCode = $info['http_code'];
488
            $message = $info['message'];
489
490
            $message = $statusCode.' : '.$message;
491
        } else {
492
            $message = "タイムアウトエラーまたはURLの指定に誤りがあります。";
493
        }
494
495
        return $message;
496
    }
497
}
498