Completed
Pull Request — master (#1632)
by Kentaro
32:45
created

Application::isTestMode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
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
24
namespace Eccube;
25
26
use Eccube\Application\ApplicationTrait;
27
use Eccube\Common\Constant;
28
use Eccube\EventListener\TransactionListener;
29
use Monolog\Logger;
30
use Symfony\Component\EventDispatcher\EventDispatcher;
31
use Symfony\Component\Finder\Finder;
32
use Symfony\Component\HttpFoundation\Request;
33
use Symfony\Component\HttpFoundation\Response;
34
use Symfony\Component\Yaml\Yaml;
35
36
class Application extends ApplicationTrait
0 ignored issues
show
introduced by
Missing class doc comment
Loading history...
37
{
38
    protected static $instance;
39
40
    protected $initialized = false;
41
    protected $initializedPlugin = false;
42
    protected $testMode = false;
43
44 604
    public static function getInstance(array $values = array())
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
45
    {
46
        if (!is_object(self::$instance)) {
47
            self::$instance = new Application($values);
48
        }
49
50 604
        return self::$instance;
51 604
    }
52
53 716
    public static function clearInstance()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
54
    {
55 716
        self::$instance = null;
56 716
    }
57
58
    final public function __clone()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
59
    {
60
        throw new \Exception('Clone is not allowed against '.get_class($this));
61
    }
62
63 717
    public function __construct(array $values = array())
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
64
    {
65
        parent::__construct($values);
66
67
        if (is_null(self::$instance)) {
68 717
            self::$instance = $this;
69
        }
70
71
        // load config
72
        $this->initConfig();
73
74
        // init monolog
75
        $this->initLogger();
76
    }
77
78 731
    public function initConfig()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
79
    {
80
        // load config
81
        $this['config'] = $this->share(function() {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
82 724
            $ymlPath = __DIR__.'/../../app/config/eccube';
83 724
            $distPath = __DIR__.'/../../src/Eccube/Resource/config';
84
85 724
            $config = array();
86 724
            $config_yml = $ymlPath.'/config.yml';
87
            if (file_exists($config_yml)) {
88
                $config = Yaml::parse(file_get_contents($config_yml));
89
            }
90
91 724
            $config_dist = array();
92 724
            $config_yml_dist = $distPath.'/config.yml.dist';
93
            if (file_exists($config_yml_dist)) {
94
                $config_dist = Yaml::parse(file_get_contents($config_yml_dist));
95
            }
96
97 724
            $config_path = array();
98 724
            $path_yml = $ymlPath.'/path.yml';
99
            if (file_exists($path_yml)) {
100
                $config_path = Yaml::parse(file_get_contents($path_yml));
101
            }
102
103 724
            $config_constant = array();
104 724
            $constant_yml = $ymlPath.'/constant.yml';
105
            if (file_exists($constant_yml)) {
106
                $config_constant = Yaml::parse(file_get_contents($constant_yml));
107
                $config_constant = empty($config_constant) ? array() : $config_constant;
108
            }
109
110 724
            $config_constant_dist = array();
111 724
            $constant_yml_dist = $distPath.'/constant.yml.dist';
112
            if (file_exists($constant_yml_dist)) {
113
                $config_constant_dist = Yaml::parse(file_get_contents($constant_yml_dist));
114
            }
115
116
            $configAll = array_replace_recursive($config_constant_dist, $config_dist, $config_constant, $config_path, $config);
117
118 724
            $database = array();
119 724
            $yml = $ymlPath.'/database.yml';
120
            if (file_exists($yml)) {
121
                $database = Yaml::parse(file_get_contents($yml));
122
            }
123
124 724
            $mail = array();
125 724
            $yml = $ymlPath.'/mail.yml';
126
            if (file_exists($yml)) {
127
                $mail = Yaml::parse(file_get_contents($yml));
128
            }
129
            $configAll = array_replace_recursive($configAll, $database, $mail);
130
131 724
            $config_log = array();
132 724
            $yml = $ymlPath.'/log.yml';
133
            if (file_exists($yml)) {
134
                $config_log = Yaml::parse(file_get_contents($yml));
135
            }
136 724
            $config_log_dist = array();
137 724
            $log_yml_dist = $distPath.'/log.yml.dist';
138
            if (file_exists($log_yml_dist)) {
139
                $config_log_dist = Yaml::parse(file_get_contents($log_yml_dist));
140
            }
141
142
            $configAll = array_replace_recursive($configAll, $config_log_dist, $config_log);
143
144 724
            $config_nav = array();
145 724
            $yml = $ymlPath.'/nav.yml';
146
            if (file_exists($yml)) {
147
                $config_nav = array('nav' => Yaml::parse(file_get_contents($yml)));
148
            }
149 724
            $config_nav_dist = array();
150 724
            $nav_yml_dist = $distPath.'/nav.yml.dist';
151
            if (file_exists($nav_yml_dist)) {
152
                $config_nav_dist = array('nav' => Yaml::parse(file_get_contents($nav_yml_dist)));
153
            }
154
155
            $configAll = array_replace_recursive($configAll, $config_nav_dist, $config_nav);
156
157 724
            return $configAll;
158
        });
159 731
    }
160
161 731
    public function initLogger()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
162
    {
163 731
        $app = $this;
164
        $this->register(new ServiceProvider\EccubeMonologServiceProvider($app));
0 ignored issues
show
Unused Code introduced by
The call to EccubeMonologServiceProvider::__construct() has too many arguments starting with $app.

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...
165
        $this['monolog.logfile'] = __DIR__.'/../../app/log/site.log';
166
        $this['monolog.name'] = 'eccube';
167 731
    }
168
169 924
    public function initialize()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
170
    {
171 924
        if ($this->initialized) {
172 203
            return;
173
        }
174
175
        // init locale
176
        $this->initLocale();
177
178
        // init session
179
        $this->initSession();
180
181
        // init twig
182
        $this->initRendering();
183
184
        // init provider
185
        $this->register(new \Silex\Provider\HttpFragmentServiceProvider());
186
        $this->register(new \Silex\Provider\UrlGeneratorServiceProvider());
187
        $this->register(new \Silex\Provider\FormServiceProvider());
188
        $this->register(new \Silex\Provider\SerializerServiceProvider());
189
        $this->register(new \Eccube\ServiceProvider\ValidatorServiceProvider());
190
191 721
        $app = $this;
192
        $this->error(function(\Exception $e, $code) use ($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
193
            if ($app['debug']) {
194 3
                return;
195
            }
196
197
            switch ($code) {
198
                case 403:
199
                    $title = 'アクセスできません。';
200
                    $message = 'お探しのページはアクセスができない状況にあるか、移動もしくは削除された可能性があります。';
201
                    break;
202
                case 404:
203
                    $title = 'ページがみつかりません。';
204
                    $message = 'URLに間違いがないかご確認ください。';
205
                    break;
206
                default:
207
                    $title = 'システムエラーが発生しました。';
208
                    $message = '大変お手数ですが、サイト管理者までご連絡ください。';
209
                    break;
210
            }
211
212
            return $app->render('error.twig', array(
213
                'error_title' => $title,
214
                'error_message' => $message,
215
            ));
216
        });
217
218
        // init mailer
219
        $this->initMailer();
220
221
        // init doctrine orm
222
        $this->initDoctrine();
223
224
        // Set up the DBAL connection now to check for a proper connection to the database.
225
        $this->checkDatabaseConnection();
226
227
        // init security
228
        $this->initSecurity();
229
230
        // init ec-cube service provider
231
        $this->register(new ServiceProvider\EccubeServiceProvider());
232
233
        // mount controllers
234
        $this->register(new \Silex\Provider\ServiceControllerServiceProvider());
235
        $this->mount('', new ControllerProvider\FrontControllerProvider());
236
        $this->mount('/'.trim($this['config']['admin_route'], '/').'/', new ControllerProvider\AdminControllerProvider());
237
        Request::enableHttpMethodParameterOverride(); // PUTやDELETEできるようにする
238
239
        // add transaction listener
240
        $this['dispatcher']->addSubscriber(new TransactionListener($this));
241
242 721
        $this->initialized = true;
243 203
    }
244
245 721
    public function initLocale()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
246
    {
247
248
        // timezone
249
        if (!empty($this['config']['timezone'])) {
250
            date_default_timezone_set($this['config']['timezone']);
251
        }
252
253
        $this->register(new \Silex\Provider\TranslationServiceProvider(), array(
254 721
            'locale' => $this['config']['locale'],
255
        ));
256
        $this['translator'] = $this->share($this->extend('translator', function($translator, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
257
            $translator->addLoader('yaml', new \Symfony\Component\Translation\Loader\YamlFileLoader());
258
259
            $r = new \ReflectionClass('Symfony\Component\Validator\Validator');
260
            $file = dirname($r->getFilename()).'/Resources/translations/validators.'.$app['locale'].'.xlf';
261
            if (file_exists($file)) {
262
                $translator->addResource('xliff', $file, $app['locale'], 'validators');
263
            }
264
265
            $file = __DIR__.'/Resource/locale/validator.'.$app['locale'].'.yml';
266
            if (file_exists($file)) {
267
                $translator->addResource('yaml', $file, $app['locale'], 'validators');
268
            }
269
270
            $file = __DIR__.'/Resource/locale/message.'.$app['locale'].'.yml';
271
            if (file_exists($file)) {
272
                $translator->addResource('yaml', $file, $app['locale']);
273
            }
274
275 399
            return $translator;
276
        }));
277 721
    }
278
279 721
    public function initSession()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
280
    {
281
        $this->register(new \Silex\Provider\SessionServiceProvider(), array(
282
            'session.storage.save_path' => $this['config']['root_dir'].'/app/cache/eccube/session',
283
            'session.storage.options' => array(
284 721
                'name' => 'eccube',
285
                'cookie_path' => $this['config']['root_urlpath'] ?: '/',
286 721
                'cookie_secure' => $this['config']['force_ssl'],
287 721
                'cookie_lifetime' => $this['config']['cookie_lifetime'],
288 721
                'cookie_httponly' => true,
289
                // cookie_domainは指定しない
290
                // http://blog.tokumaru.org/2011/10/cookiedomain.html
291 721
            ),
292
        ));
293 721
    }
294
295 721
    public function initRendering()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
296
    {
297
        $this->register(new \Silex\Provider\TwigServiceProvider(), array(
298
            'twig.form.templates' => array('Form/form_layout.twig'),
299
        ));
300
        $this['twig'] = $this->share($this->extend('twig', function(\Twig_Environment $twig, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
301
            $twig->addExtension(new \Eccube\Twig\Extension\EccubeExtension($app));
302
            $twig->addExtension(new \Twig_Extension_StringLoader());
303
304 158
            return $twig;
305
        }));
306
307
        $this->before(function(Request $request, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
308
            // フロント or 管理画面ごとにtwigの探索パスを切り替える.
309
            $app['twig'] = $app->share($app->extend('twig', function(\Twig_Environment $twig, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
310 130
                $paths = array();
311
312
                // 互換性がないのでprofiler とproduction 時のcacheを分離する
313
314
                $app['admin'] = false;
315
                $app['front'] = false;
316
317
                if (isset($app['profiler'])) {
318
                    $cacheBaseDir = __DIR__.'/../../app/cache/twig/profiler/';
319
                } else {
320 130
                    $cacheBaseDir = __DIR__.'/../../app/cache/twig/production/';
321
                }
322
                $pathinfo = rawurldecode($app['request']->getPathInfo());
323
                if (strpos($pathinfo, '/'.trim($app['config']['admin_route'], '/').'/') === 0) {
324
                    if (file_exists(__DIR__.'/../../app/template/admin')) {
325 80
                        $paths[] = __DIR__.'/../../app/template/admin';
326
                    }
327
                    $paths[] = $app['config']['template_admin_realdir'];
328 80
                    $paths[] = __DIR__.'/../../app/Plugin';
329 80
                    $cache = $cacheBaseDir.'admin';
330
                    $app['admin'] = true;
331
                } else {
332
                    if (file_exists($app['config']['template_realdir'])) {
333
                        $paths[] = $app['config']['template_realdir'];
334
                    }
335
                    $paths[] = $app['config']['template_default_realdir'];
336 50
                    $paths[] = __DIR__.'/../../app/Plugin';
337
                    $cache = $cacheBaseDir.$app['config']['template_code'];
338
                    $app['front'] = true;
339 80
                }
340
                $twig->setCache($cache);
341
                $app['twig.loader']->addLoader(new \Twig_Loader_Filesystem($paths));
342
343 130
                return $twig;
344
            }));
345
346
            // 管理画面のIP制限チェック.
347
            $pathinfo = rawurldecode($app['request']->getPathInfo());
348
            if (strpos($pathinfo, '/'.trim($app['config']['admin_route'], '/').'/') === 0) {
349
                // IP制限チェック
350
                $allowHost = $app['config']['admin_allow_host'];
351
                if (count($allowHost) > 0) {
352
                    if (array_search($app['request']->getClientIp(), $allowHost) === false) {
353
                        throw new \Exception();
354
                    }
355
                }
356
            }
357
        }, self::EARLY_EVENT);
358
359
        // twigのグローバル変数を定義.
360 721
        $app = $this;
361
        $this->on(\Symfony\Component\HttpKernel\KernelEvents::CONTROLLER, function(\Symfony\Component\HttpKernel\Event\FilterControllerEvent $event) use ($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
362
            // ショップ基本情報
363
            $BaseInfo = $app['eccube.repository.base_info']->get();
364
            $app['twig']->addGlobal('BaseInfo', $BaseInfo);
365
366
            $pathinfo = rawurldecode($app['request']->getPathInfo());
367
            if (strpos($pathinfo, '/'.trim($app['config']['admin_route'], '/').'/') === 0) {
368
                // 管理画面
369
                // 管理画面メニュー
370 80
                $menus = array('', '', '');
371
                $app['twig']->addGlobal('menus', $menus);
372
373
                $Member = $app->user();
374
                if (is_object($Member)) {
375
                    // ログインしていれば管理者のロールを取得
376
                    $AuthorityRoles = $app['eccube.repository.authority_role']->findBy(array('Authority' => $Member->getAuthority()));
377
378 77
                    $roles = array();
379
                    foreach ($AuthorityRoles as $AuthorityRole) {
380
                        // 管理画面でメニュー制御するため相対パス全てをセット
381
                        $roles[] = $app['request']->getBaseUrl().'/'.$app['config']['admin_route'].$AuthorityRole->getDenyUrl();
382 77
                    }
383
384
                    $app['twig']->addGlobal('AuthorityRoles', $roles);
385
                }
386
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
387
            } else {
388
                // フロント画面
389
                $request = $event->getRequest();
390
                $route = $request->attributes->get('_route');
391
392
                // ユーザ作成画面
393
                if ($route === trim($app['config']['user_data_route'])) {
394
                    $params = $request->attributes->get('_route_params');
395
                    $route = $params['route'];
396
                    // プレビュー画面
397
                } elseif ($request->get('preview')) {
398
                    $route = 'preview';
399
                }
400
401
                try {
402
                    $DeviceType = $app['eccube.repository.master.device_type']
403
                        ->find(\Eccube\Entity\Master\DeviceType::DEVICE_TYPE_PC);
404
                    $PageLayout = $app['eccube.repository.page_layout']->getByUrl($DeviceType, $route);
405
                } catch (\Doctrine\ORM\NoResultException $e) {
406
                    $PageLayout = $app['eccube.repository.page_layout']->newPageLayout($DeviceType);
407 29
                }
408
409
                $app['twig']->addGlobal('PageLayout', $PageLayout);
410
                $app['twig']->addGlobal('title', $PageLayout->getName());
411 80
            }
412
        });
413 721
    }
414
415 721
    public function initMailer()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
416
    {
417
418
        // メール送信時の文字エンコード指定(デフォルトはUTF-8)
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% 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...
419
        if (isset($this['config']['mail']['charset_iso_2022_jp']) && is_bool($this['config']['mail']['charset_iso_2022_jp'])) {
420
            if ($this['config']['mail']['charset_iso_2022_jp'] === true) {
421
                \Swift::init(function() {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
422
                    \Swift_DependencyContainer::getInstance()
423
                        ->register('mime.qpheaderencoder')
424
                        ->asAliasOf('mime.base64headerencoder');
425
                    \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
426
                });
427
            }
428
        }
429
430
        $this->register(new \Silex\Provider\SwiftmailerServiceProvider());
431
        $this['swiftmailer.options'] = $this['config']['mail'];
432
433
        if (isset($this['config']['mail']['spool']) && is_bool($this['config']['mail']['spool'])) {
434
            $this['swiftmailer.use_spool'] = $this['config']['mail']['spool'];
435
        }
436
        // デフォルトはsmtpを使用
437
        $transport = $this['config']['mail']['transport'];
438 721
        if ($transport == 'sendmail') {
439
            $this['swiftmailer.transport'] = \Swift_SendmailTransport::newInstance();
440 721
        } elseif ($transport == 'mail') {
441
            $this['swiftmailer.transport'] = \Swift_MailTransport::newInstance();
442
        }
443 721
    }
444
445 715
    public function initDoctrine()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
446
    {
447
        $this->register(new \Silex\Provider\DoctrineServiceProvider(), array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
448
            'dbs.options' => array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
449 715
                'default' => $this['config']['database']
450
        )));
451
        $this->register(new \Saxulum\DoctrineOrmManagerRegistry\Silex\Provider\DoctrineOrmManagerRegistryProvider());
452
453
        // プラグインのmetadata定義を合わせて行う.
454 715
        $pluginBasePath = __DIR__.'/../../app/Plugin';
455 714
        $finder = Finder::create()
456 715
            ->in($pluginBasePath)
457 715
            ->directories()
458
            ->depth(0);
459
460 715
        $ormMappings = array();
461
        $ormMappings[] = array(
462 715
            'type' => 'yml',
463 715
            'namespace' => 'Eccube\Entity',
464
            'path' => array(
465 715
                __DIR__.'/Resource/doctrine',
466 715
                __DIR__.'/Resource/doctrine/master',
467 715
            ),
468 715
        );
469
470
        foreach ($finder as $dir) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
471
472
            $file = $dir->getRealPath().'/config.yml';
473
474
            if (file_exists($file)) {
475
                $config = Yaml::parse(file_get_contents($file));
476
            } else {
477
                $code = $dir->getBaseName();
478
                $this['monolog']->warning("skip {$code} orm.path loading. config.yml not found.", array('path' => $file));
479
                continue;
480 138
            }
481
482
            // Doctrine Extend
483 138
            if (isset($config['orm.path']) && is_array($config['orm.path'])) {
484
                $paths = array();
485
                foreach ($config['orm.path'] as $path) {
486
                    $paths[] = $pluginBasePath.'/'.$config['code'].$path;
487
                }
488
                $ormMappings[] = array(
489
                    'type' => 'yml',
490
                    'namespace' => 'Plugin\\'.$config['code'].'\\Entity',
491
                    'path' => $paths,
492
                );
493
            }
494 577
        }
495
496
        $this->register(new \Dflydev\Silex\Provider\DoctrineOrm\DoctrineOrmServiceProvider(), array(
497 715
            'orm.proxies_dir' => __DIR__.'/../../app/cache/doctrine',
498
            'orm.em.options' => array(
499
                'mappings' => $ormMappings,
500 715
            ),
501
        ));
502 715
    }
503
504 721
    public function initSecurity()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
505
    {
506
        $this->register(new \Silex\Provider\SecurityServiceProvider());
507
        $this->register(new \Silex\Provider\RememberMeServiceProvider());
508
509 721
        $this['security.firewalls'] = array(
510
            'admin' => array(
511
                'pattern' => "^/{$this['config']['admin_route']}/",
512
                'form' => array(
513
                    'login_path' => "/{$this['config']['admin_route']}/login",
514
                    'check_path' => "/{$this['config']['admin_route']}/login_check",
515 721
                    'username_parameter' => 'login_id',
516 721
                    'password_parameter' => 'password',
517 721
                    'with_csrf' => true,
518 721
                    'use_forward' => true,
519 721
                ),
520
                'logout' => array(
521
                    'logout_path' => "/{$this['config']['admin_route']}/logout",
522
                    'target_url' => "/{$this['config']['admin_route']}/",
523 721
                ),
524
                'users' => $this['orm.em']->getRepository('Eccube\Entity\Member'),
525 721
                'anonymous' => true,
526
            ),
527
            'customer' => array(
528 721
                'pattern' => '^/',
529
                'form' => array(
530
                    'login_path' => '/mypage/login',
531
                    'check_path' => '/login_check',
532
                    'username_parameter' => 'login_email',
533
                    'password_parameter' => 'login_pass',
534
                    'with_csrf' => true,
535
                    'use_forward' => true,
536 721
                ),
537
                'logout' => array(
538
                    'logout_path' => '/logout',
539
                    'target_url' => '/',
540 721
                ),
541
                'remember_me' => array(
542
                    'key' => sha1($this['config']['auth_magic']),
543 721
                    'name' => 'eccube_rememberme',
544
                    // lifetimeはデフォルトの1年間にする
545
                    // 'lifetime' => $this['config']['cookie_lifetime'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% 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...
546
                    'path' => $this['config']['root_urlpath'] ?: '/',
547 721
                    'secure' => $this['config']['force_ssl'],
548 721
                    'httponly' => true,
549 721
                    'always_remember_me' => false,
550 721
                    'remember_me_parameter' => 'login_memory',
551
                ),
552
                'users' => $this['orm.em']->getRepository('Eccube\Entity\Customer'),
553 721
                'anonymous' => true,
554
            ),
555
        );
556
557 721
        $this['security.access_rules'] = array(
558
            array("^/{$this['config']['admin_route']}/login", 'IS_AUTHENTICATED_ANONYMOUSLY'),
559
            array("^/{$this['config']['admin_route']}/", 'ROLE_ADMIN'),
560 721
            array('^/mypage/login', 'IS_AUTHENTICATED_ANONYMOUSLY'),
561 721
            array('^/mypage/withdraw_complete', 'IS_AUTHENTICATED_ANONYMOUSLY'),
562 721
            array('^/mypage/change', 'IS_AUTHENTICATED_FULLY'),
563 721
            array('^/mypage', 'ROLE_USER'),
564
        );
565
566
        $this['eccube.password_encoder'] = $this->share(function($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
567
            return new \Eccube\Security\Core\Encoder\PasswordEncoder($app['config']);
568
        });
569
        $this['security.encoder_factory'] = $this->share(function($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
570
            return new \Symfony\Component\Security\Core\Encoder\EncoderFactory(array(
571 720
                'Eccube\Entity\Customer' => $app['eccube.password_encoder'],
572 720
                'Eccube\Entity\Member' => $app['eccube.password_encoder'],
573
            ));
574
        });
575
        $this['eccube.event_listner.security'] = $this->share(function($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
576
            return new \Eccube\EventListener\SecurityEventListener($app['orm.em']);
577
        });
578
        $this['user'] = function($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
579
            $token = $app['security']->getToken();
580
581
            return ($token !== null) ? $token->getUser() : null;
582
        };
583
584
        // ログイン時のイベントを設定.
585
        $this['dispatcher']->addListener(\Symfony\Component\Security\Http\SecurityEvents::INTERACTIVE_LOGIN, array($this['eccube.event_listner.security'], 'onInteractiveLogin'));
586
587
        // Voterの設定
588 721
        $app = $this;
589
        $this['authority_voter'] = $this->share(function($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
590
            return new \Eccube\Security\Voter\AuthorityVoter($app);
591
        });
592
593
        $app['security.voters'] = $app->extend('security.voters', function($voters) use ($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
594
            $voters[] = $app['authority_voter'];
595
596 720
            return $voters;
597
        });
598
599
        $this['security.access_manager'] = $this->share(function($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
600
            return new \Symfony\Component\Security\Core\Authorization\AccessDecisionManager($app['security.voters'], 'unanimous');
601
        });
602
603 721
    }
604
605 924
    public function initializePlugin()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
606
    {
607 924
        if ($this->initializedPlugin) {
608
            return;
609
        }
610
611
        // setup event dispatcher
612
        $this->initPluginEventDispatcher();
613
614
        // load plugin
615 204
        $this->loadPlugin();
616
617 720
        $this->initializedPlugin = true;
618 720
    }
619
620 4
    public function initPluginEventDispatcher()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
621
    {
622
        // EventDispatcher
623
        $this['eccube.event.dispatcher'] = $this->share(function() {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
624
            return new EventDispatcher();
625
        });
626
627
        // hook point
628
        $this->before(function(Request $request, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
629
            $app['eccube.event.dispatcher']->dispatch('eccube.event.app.before');
630
        }, self::EARLY_EVENT);
631
632
        $this->before(function(Request $request, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
633
            $event = 'eccube.event.controller.'.$request->attributes->get('_route').'.before';
634
            $app['eccube.event.dispatcher']->dispatch($event);
635
        });
636
637 View Code Duplication
        $this->after(function(Request $request, Response $response, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
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...
638
            $event = 'eccube.event.controller.'.$request->attributes->get('_route').'.after';
639
            $app['eccube.event.dispatcher']->dispatch($event);
640
        });
641
642
        $this->after(function(Request $request, Response $response, \Silex\Application $app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
643
            $app['eccube.event.dispatcher']->dispatch('eccube.event.app.after');
644
        }, self::LATE_EVENT);
645
646 View Code Duplication
        $this->finish(function(Request $request, Response $response, \Silex\Application $app) {
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...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
647
            $event = 'eccube.event.controller.'.$request->attributes->get('_route').'.finish';
648
            $app['eccube.event.dispatcher']->dispatch($event);
649
        });
650
651 4
        $app = $this;
652
        $this->on(\Symfony\Component\HttpKernel\KernelEvents::RESPONSE, function(\Symfony\Component\HttpKernel\Event\FilterResponseEvent $event) use ($app) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
653
            $route = $event->getRequest()->attributes->get('_route');
654
            $app['eccube.event.dispatcher']->dispatch('eccube.event.render.'.$route.'.before', $event);
655
        });
656
657
        // Request Event
658 View Code Duplication
        $this->on(\Symfony\Component\HttpKernel\KernelEvents::REQUEST, function(\Symfony\Component\HttpKernel\Event\GetResponseEvent $event) use ($app) {
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...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
659
660
            if (\Symfony\Component\HttpKernel\HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
661
                return;
662
            }
663
664
            $route = $event->getRequest()->attributes->get('_route');
665
666
            if (is_null($route)) {
667
                return;
668
            }
669
670
            $app['monolog']->debug('KernelEvents::REQUEST '.$route);
671
672
            // 全体
673
            $app['eccube.event.dispatcher']->dispatch('eccube.event.app.request', $event);
674
675
            if (strpos($route, 'admin') === 0) {
676
                // 管理画面
677
                $app['eccube.event.dispatcher']->dispatch('eccube.event.admin.request', $event);
678
            } else {
679
                // フロント画面
680
                $app['eccube.event.dispatcher']->dispatch('eccube.event.front.request', $event);
681
            }
682
683
            // ルーティング単位
684
            $app['eccube.event.dispatcher']->dispatch("eccube.event.route.{$route}.request", $event);
685
686
        }, 30); // Routing(32)が解決しし, 認証判定(8)が実行される前のタイミング.
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
687
688
        // Controller Event
689 View Code Duplication
        $this->on(\Symfony\Component\HttpKernel\KernelEvents::CONTROLLER, function(\Symfony\Component\HttpKernel\Event\FilterControllerEvent $event) use ($app) {
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...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
690
691
            if (\Symfony\Component\HttpKernel\HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
692
                return;
693
            }
694
695
696
            $route = $event->getRequest()->attributes->get('_route');
697
698
            if (is_null($route)) {
699
                return;
700
            }
701
702
            $app['monolog']->debug('KernelEvents::CONTROLLER '.$route);
703
704
            // 全体
705
            $app['eccube.event.dispatcher']->dispatch('eccube.event.app.controller', $event);
706
707
            if (strpos($route, 'admin') === 0) {
708
                // 管理画面
709
                $app['eccube.event.dispatcher']->dispatch('eccube.event.admin.controller', $event);
710
            } else {
711
                // フロント画面
712
                $app['eccube.event.dispatcher']->dispatch('eccube.event.front.controller', $event);
713
            }
714
715
            // ルーティング単位
716
            $app['eccube.event.dispatcher']->dispatch("eccube.event.route.{$route}.controller", $event);
717
        });
718
719
        // Response Event
720 View Code Duplication
        $this->on(\Symfony\Component\HttpKernel\KernelEvents::RESPONSE, function(\Symfony\Component\HttpKernel\Event\FilterResponseEvent $event) use ($app) {
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...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
721
722
            if (\Symfony\Component\HttpKernel\HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
723
                return;
724
            }
725
726
            $route = $event->getRequest()->attributes->get('_route');
727
728
            if (is_null($route)) {
729
                return;
730
            }
731
732
            $app['monolog']->debug('KernelEvents::RESPONSE '.$route);
733
734
            // ルーティング単位
735
            $app['eccube.event.dispatcher']->dispatch("eccube.event.route.{$route}.response", $event);
736
737
            if (strpos($route, 'admin') === 0) {
738
                // 管理画面
739
                $app['eccube.event.dispatcher']->dispatch('eccube.event.admin.response', $event);
740
            } else {
741
                // フロント画面
742
                $app['eccube.event.dispatcher']->dispatch('eccube.event.front.response', $event);
743
            }
744
745
            // 全体
746
            $app['eccube.event.dispatcher']->dispatch('eccube.event.app.response', $event);
747
        });
748
749
        // Exception Event
750 View Code Duplication
        $this->on(\Symfony\Component\HttpKernel\KernelEvents::EXCEPTION, function(\Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event) use ($app) {
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...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
751
752
            if (\Symfony\Component\HttpKernel\HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
753
                return;
754
            }
755
756
            $route = $event->getRequest()->attributes->get('_route');
757
758
            if (is_null($route)) {
759
                return;
760
            }
761
762
            $app['monolog']->debug('KernelEvents::EXCEPTION '.$route);
763
764
            // ルーティング単位
765
            $app['eccube.event.dispatcher']->dispatch("eccube.event.route.{$route}.exception", $event);
766
767
            if (strpos($route, 'admin') === 0) {
768
                // 管理画面
769
                $app['eccube.event.dispatcher']->dispatch('eccube.event.admin.exception', $event);
770
            } else {
771
                // フロント画面
772
                $app['eccube.event.dispatcher']->dispatch('eccube.event.front.exception', $event);
773
            }
774
775
            // 全体
776
            $app['eccube.event.dispatcher']->dispatch('eccube.event.app.exception', $event);
777
        });
778
779
        // Terminate Event
780 View Code Duplication
        $this->on(\Symfony\Component\HttpKernel\KernelEvents::TERMINATE, function(\Symfony\Component\HttpKernel\Event\PostResponseEvent $event) use ($app) {
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...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
781
782
            $route = $event->getRequest()->attributes->get('_route');
783
784
            if (is_null($route)) {
785
                return;
786
            }
787
788
            $app['monolog']->debug('KernelEvents::TERMINATE '.$route);
789
790
            // ルーティング単位
791
            $app['eccube.event.dispatcher']->dispatch("eccube.event.route.{$route}.terminate", $event);
792
793
            if (strpos($route, 'admin') === 0) {
794
                // 管理画面
795
                $app['eccube.event.dispatcher']->dispatch('eccube.event.admin.terminate', $event);
796
            } else {
797
                // フロント画面
798
                $app['eccube.event.dispatcher']->dispatch('eccube.event.front.terminate', $event);
799
            }
800
801
            // 全体
802
            $app['eccube.event.dispatcher']->dispatch('eccube.event.app.terminate', $event);
803
        });
804 4
    }
805
806 924
    public function loadPlugin()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
807
    {
808
        // プラグインディレクトリを探索.
809 924
        $basePath = __DIR__.'/../../app/Plugin';
810 924
        $finder = Finder::create()
811 924
            ->in($basePath)
812 924
            ->directories()
813
            ->depth(0);
814
815
        $finder->sortByName();
816
817
        // ハンドラ優先順位をdbから持ってきてハッシュテーブルを作成
818 924
        $priorities = array();
819
        $handlers = $this['orm.em']
820
            ->getRepository('Eccube\Entity\PluginEventHandler')
821 204
            ->getHandlers();
822
        foreach ($handlers as $handler) {
823
            if ($handler->getPlugin()->getEnable() && !$handler->getPlugin()->getDelFlg()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
824
825
                $priority = $handler->getPriority();
826
            } else {
827
                // Pluginがdisable、削除済みの場合、EventHandlerのPriorityを全て0とみなす
828
                $priority = \Eccube\Entity\PluginEventHandler::EVENT_PRIORITY_DISABLED;
829
            }
830
            $priorities[$handler->getPlugin()->getClassName()][$handler->getEvent()][$handler->getHandler()] = $priority;
831 720
        }
832
833
        // プラグインをロードする.
834
        // config.yml/event.ymlの定義に沿ってインスタンスの生成を行い, イベント設定を行う.
835
        foreach ($finder as $dir) {
836
            //config.ymlのないディレクトリは無視する
837
            $path = $dir->getRealPath();
838
            $code = $dir->getBaseName();
839
            try {
840
                $this['eccube.service.plugin']->checkPluginArchiveContent($path);
841
            } catch (\Eccube\Exception\PluginException $e) {
842
                $this['monolog']->warning("skip {$code} config loading. config.yml not foud or invalid.", array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
843
                    'path' =>  $path,
844
                    'original-message' => $e->getMessage()
845
                ));
846
                continue;
847 138
            }
848
            $config = $this['eccube.service.plugin']->readYml($dir->getRealPath().'/config.yml');
849
850
            $plugin = $this['orm.em']
851
                ->getRepository('Eccube\Entity\Plugin')
852
                ->findOneBy(array('code' => $config['code']));
853
854
            // const
855 138
            if (isset($config['const'])) {
856
                $this['config'] = $this->share($this->extend('config', function($eccubeConfig) use ($config) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
857
                    $eccubeConfig[$config['code']] = array(
858
                        'const' => $config['const'],
859
                    );
860
861
                    return $eccubeConfig;
862
                }));
863
            }
864
865 138
            if ($plugin && $plugin->getEnable() == Constant::DISABLED) {
866
                // プラグインが無効化されていれば読み込まない
867
                continue;
868
            }
869
870
            // Type: Event
871 138
            if (isset($config['event'])) {
872 138
                $class = '\\Plugin\\'.$config['code'].'\\'.$config['event'];
873 138
                $eventExists = true;
874
875 View Code Duplication
                if (!class_exists($class)) {
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...
876
                    $this['monolog']->warning("skip {$code} loading. event class not foud.", array(
877
                        'class' =>  $class,
878
                    ));
879
                    $eventExists = false;
880
                }
881
882
                if ($eventExists && file_exists($dir->getRealPath().'/event.yml')) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
883
884
                    $subscriber = new $class($this);
885
886
                    foreach (Yaml::parse(file_get_contents($dir->getRealPath().'/event.yml')) as $event => $handlers) {
887
                        foreach ($handlers as $handler) {
888
                            if (!isset($priorities[$config['event']][$event][$handler[0]])) { // ハンドラテーブルに登録されていない(ソースにしか記述されていない)ハンドラは一番後ろにする
889
                                $priority = \Eccube\Entity\PluginEventHandler::EVENT_PRIORITY_LATEST;
890
                            } else {
891
                                $priority = $priorities[$config['event']][$event][$handler[0]];
892
                            }
893
                            // 優先度が0のプラグインは登録しない
894
                            if (\Eccube\Entity\PluginEventHandler::EVENT_PRIORITY_DISABLED != $priority) {
895
                                $this['eccube.event.dispatcher']->addListener($event, array($subscriber, $handler[0]), $priority);
896
                            }
897
                        }
898
                    }
899
                }
900
            }
901
            // Type: ServiceProvider
902 138
            if (isset($config['service'])) {
903
                foreach ($config['service'] as $service) {
904
                    $class = '\\Plugin\\'.$config['code'].'\\ServiceProvider\\'.$service;
905 View Code Duplication
                    if (!class_exists($class)) {
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...
906
                        $this['monolog']->warning("skip {$code} loading. service provider class not foud.", array(
907
                            'class' =>  $class,
908
                        ));
909
                        continue;
910
                    }
911
                    $this->register(new $class($this));
912
                }
913
            }
914 582
        }
915 204
    }
916
917
    /**
918
     * PHPUnit を実行中かどうかを設定する.
919
     *
920
     * @param boolean $testMode PHPUnit を実行中の場合 true
921
     */
922 709
    public function setTestMode($testMode) {
923 709
        $this->testMode = $testMode;
924 709
    }
925
926
    /**
927
     * PHPUnit を実行中かどうか.
928
     *
929
     * @return boolean PHPUnit を実行中の場合 true
930
     */
931
    public function isTestMode()
932
    {
933
        return $this->testMode;
934
    }
935
936
    /**
937
     *
938
     * データベースの接続を確認
939
     * 成功 : trueを返却
940
     * 失敗 : \Doctrine\DBAL\DBALExceptionエラーが発生( 接続に失敗した場合 )、エラー画面を表示しdie()
941
     * 備考 : app['debug']がtrueの際は処理を行わない
942
     * @return boolean true
943
     *
944
     */
945 717
    protected function checkDatabaseConnection()
946
    {
947
        if ($this['debug']) {
948 717
            return;
949
        }
950
        try {
951
            $this['db']->connect();
952
        } catch (\Doctrine\DBAL\DBALException $e) {
953
            $this['monolog']->error($e->getMessage());
954
            $this['twig.path'] = array(__DIR__.'/Resource/template/exception');
955
            $html = $this['twig']->render('error.twig', array(
956
                'error_title' => 'データーベース接続エラー',
957
                'error_message' => 'データーベースを確認してください',
958
            ));
959
            $response = new Response();
960
            $response->setContent($html);
961
            $response->setStatusCode('500');
962
            $response->headers->set('Content-Type', 'text/html');
963
            $response->send();
964
            die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkDatabaseConnection() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
965 4
        }
966 4
        return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
967
    }
968
}
969