Completed
Push — master ( 324e90...8b0047 )
by Tim
9s
created

Configuration::isIgnorePid()   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 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Cli\Configuration
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import-cli-simple
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Cli;
22
23
use Psr\Log\LogLevel;
24
use JMS\Serializer\Annotation\Type;
25
use JMS\Serializer\SerializerBuilder;
26
use JMS\Serializer\Annotation\SerializedName;
27
use TechDivision\Import\ConfigurationInterface;
28
use Symfony\Component\Console\Input\InputInterface;
29
use TechDivision\Import\Cli\Command\InputOptionKeys;
30
use TechDivision\Import\Cli\Command\InputArgumentKeys;
31
use TechDivision\Import\Cli\Configuration\Operation;
32
33
/**
34
 * A simple configuration implementation.
35
 *
36
 * @author    Tim Wagner <[email protected]>
37
 * @copyright 2016 TechDivision GmbH <[email protected]>
38
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
39
 * @link      https://github.com/techdivision/import-cli-simple
40
 * @link      http://www.techdivision.com
41
 */
42
class Configuration implements ConfigurationInterface
43
{
44
45
    /**
46
     * Mapping for boolean values passed on the console.
47
     *
48
     * @var array
49
     */
50
    protected $booleanMapping = array(
51
        'true'  => true,
52
        'false' => false,
53
        '1'     => true,
54
        '0'     => false,
55
        'on'    => true,
56
        'off'   => false
57
    );
58
59
    /**
60
     * The operation name to use.
61
     *
62
     * @var string
63
     * @Type("string")
64
     * @SerializedName("operation-name")
65
     */
66
    protected $operationName;
67
68
    /**
69
     * The Magento edition, EE or CE.
70
     *
71
     * @var string
72
     * @Type("string")
73
     * @SerializedName("magento-edition")
74
     */
75
    protected $magentoEdition = 'CE';
76
77
    /**
78
     * The Magento version, e. g. 2.1.0.
79
     *
80
     * @var string
81
     * @Type("string")
82
     * @SerializedName("magento-version")
83
     */
84
    protected $magentoVersion = '2.1.2';
85
86
    /**
87
     * The Magento installation directory.
88
     *
89
     * @var string
90
     * @Type("string")
91
     * @SerializedName("installation-dir")
92
     */
93
    protected $installationDir;
94
95
    /**
96
     * The source directory that has to be watched for new files.
97
     *
98
     * @var string
99
     * @Type("string")
100
     * @SerializedName("source-dir")
101
     */
102
    protected $sourceDir;
103
104
    /**
105
     * The target directory with the files that has been imported.
106
     *
107
     * @var string
108
     * @Type("string")
109
     * @SerializedName("target-dir")
110
     */
111
    protected $targetDir;
112
113
    /**
114
     * The database configuration.
115
     *
116
     * @var TechDivision\Import\Configuration\Database
117
     * @Type("TechDivision\Import\Cli\Configuration\Database")
118
     */
119
    protected $database;
120
121
    /**
122
     * ArrayCollection with the information of the configured operations.
123
     *
124
     * @var \Doctrine\Common\Collections\ArrayCollection
125
     * @Type("ArrayCollection<TechDivision\Import\Cli\Configuration\Operation>")
126
     */
127
    protected $operations;
128
129
    /**
130
     * The subject's utility class with the SQL statements to use.
131
     *
132
     * @var string
133
     * @Type("string")
134
     * @SerializedName("utility-class-name")
135
     */
136
    protected $utilityClassName;
137
138
    /**
139
     * The source date format to use in the subject.
140
     *
141
     * @var string
142
     * @Type("string")
143
     * @SerializedName("source-date-format")
144
     */
145
    protected $sourceDateFormat = 'n/d/y, g:i A';
146
147
    /**
148
     * The subject's multiple field delimiter character for fields with multiple values, defaults to (,).
149
     *
150
     * @var string
151
     * @Type("string")
152
     * @SerializedName("multiple-field-delimiter")
153
     */
154
    protected $multipleFieldDelimiter = ',';
155
156
    /**
157
     * The subject's delimiter character for CSV files.
158
     *
159
     * @var string
160
     * @Type("string")
161
     */
162
    protected $delimiter;
163
164
    /**
165
     * The subject's enclosure character for CSV files.
166
     *
167
     * @var string
168
     * @Type("string")
169
     */
170
    protected $enclosure;
171
172
    /**
173
     * The subject's escape character for CSV files.
174
     *
175
     * @var string
176
     * @Type("string")
177
     */
178
    protected $escape;
179
180
    /**
181
     * The subject's source charset for the CSV file.
182
     *
183
     * @var string
184
     * @Type("string")
185
     * @SerializedName("from-charset")
186
     */
187
    protected $fromCharset;
188
189
    /**
190
     * The subject's target charset for a CSV file.
191
     *
192
     * @var string
193
     * @Type("string")
194
     * @SerializedName("to-charset")
195
     */
196
    protected $toCharset;
197
198
    /**
199
     * The subject's file mode for a CSV target file.
200
     *
201
     * @var string
202
     * @Type("string")
203
     * @SerializedName("file-mode")
204
     */
205
    protected $fileMode;
206
207
    /**
208
     * The flag to signal that the subject has to use the strict mode or not.
209
     *
210
     * @var boolean
211
     * @Type("boolean")
212
     * @SerializedName("strict-mode")
213
     */
214
    protected $strictMode;
215
216
    /**
217
     * The flag whether or not the import artefacts have to be archived.
218
     *
219
     * @var boolean
220
     * @Type("boolean")
221
     * @SerializedName("archive-artefacts")
222
     */
223
    protected $archiveArtefacts;
224
225
    /**
226
     * The directory where the archives will be stored.
227
     *
228
     * @var string
229
     * @Type("string")
230
     * @SerializedName("archive-dir")
231
     */
232
    protected $archiveDir;
233
234
    /**
235
     * The flag to signal that the subject has to use the debug mode or not.
236
     *
237
     * @var boolean
238
     * @Type("boolean")
239
     * @SerializedName("debug-mode")
240
     */
241
    protected $debugMode = false;
242
243
    /**
244
     * The flag to signal that the an existing PID should be ignored, whether or import process is running or not.
245
     *
246
     * @var boolean
247
     * @Type("boolean")
248
     * @SerializedName("ignore-pid")
249
     */
250
    protected $ignorePid = false;
251
252
    /**
253
     * The log level to use (see Monolog documentation).
254
     *
255
     * @var string
256
     * @Type("string")
257
     * @SerializedName("log-level")
258
     */
259
    protected $logLevel = LogLevel::INFO;
260
261
    /**
262
     * Factory implementation to create a new initialized configuration instance.
263
     *
264
     * If command line options are specified, they will always override the
265
     * values found in the configuration file.
266
     *
267
     * @param \Symfony\Component\Console\Input\InputInterface $input The Symfony console input instance
268
     *
269
     * @return \TechDivision\Import\Cli\Configuration The configuration instance
270
     * @throws \Exception Is thrown, if the specified configuration file doesn't exist
271
     */
272
    public static function factory(InputInterface $input)
273
    {
274
275
        // load the configuration filename we want to use
276
        $filename = $input->getOption(InputOptionKeys::CONFIGURATION);
277
278
        // load the JSON data
279
        if (!$jsonData = file_get_contents($filename)) {
280
            throw new \Exception(sprintf('Can\'t load configuration file %s', $filename));
281
        }
282
283
        // initialize the JMS serializer and load the configuration
284
        $serializer = SerializerBuilder::create()->build();
285
        /** @var \TechDivision\Import\Cli\Configuration $instance */
286
        $instance = $serializer->deserialize($jsonData, 'TechDivision\Import\Cli\Configuration', 'json');
287
288
        // query whether or not an operation name has been specified as command line
289
        // option, if yes override the value from the configuration file
290
        if ($operationName = $input->getArgument(InputArgumentKeys::OPERATION_NAME)) {
291
            $instance->setOperationName($operationName);
292
        }
293
294
        // query whether or not a Magento installation directory has been specified as command line
295
        // option, if yes override the value from the configuration file
296
        if ($installationDir = $input->getOption(InputOptionKeys::INSTALLATION_DIR)) {
297
            $instance->setInstallationDir($installationDir);
298
        }
299
300
        // query whether or not a directory for the source files has been specified as command line
301
        // option, if yes override the value from the configuration file
302
        if ($sourceDir = $input->getOption(InputOptionKeys::SOURCE_DIR)) {
303
            $instance->setSourceDir($sourceDir);
304
        }
305
306
        // query whether or not a directory containing the imported files has been specified as command line
307
        // option, if yes override the value from the configuration file
308
        if ($targetDir = $input->getOption(InputOptionKeys::TARGET_DIR)) {
309
            $instance->setTargetDir($targetDir);
310
        }
311
312
        // query whether or not a source date format has been specified as command
313
        // line  option, if yes override the value from the configuration file
314
        if ($sourceDateFormat = $input->getOption(InputOptionKeys::SOURCE_DATE_FORMAT)) {
315
            $instance->setSourceDateFormat($sourceDateFormat);
316
        }
317
318
        // query whether or not a Magento edition has been specified as command line
319
        // option, if yes override the value from the configuration file
320
        if ($magentoEdition = $input->getOption(InputOptionKeys::MAGENTO_EDITION)) {
321
            $instance->setMagentoEdition($magentoEdition);
322
        }
323
324
        // query whether or not a Magento version has been specified as command line
325
        // option, if yes override the value from the configuration file
326
        if ($magentoVersion = $input->getOption(InputOptionKeys::MAGENTO_VERSION)) {
327
            $instance->setMagentoVersion($magentoVersion);
328
        }
329
330
        // query whether or not a PDO DSN has been specified as command line
331
        // option, if yes override the value from the configuration file
332
        if ($dsn = $input->getOption(InputOptionKeys::DB_PDO_DSN)) {
333
            $instance->getDatabase()->setDsn($dsn);
334
        }
335
336
        // query whether or not a DB username has been specified as command line
337
        // option, if yes override the value from the configuration file
338
        if ($username = $input->getOption(InputOptionKeys::DB_USERNAME)) {
339
            $instance->getDatabase()->setUsername($username);
340
        }
341
342
        // query whether or not a DB password has been specified as command line
343
        // option, if yes override the value from the configuration file
344
        if ($password = $input->getOption(InputOptionKeys::DB_PASSWORD)) {
345
            $instance->getDatabase()->setPassword($password);
346
        }
347
348
        // query whether or not the debug mode has been specified as command line
349
        // option, if yes override the value from the configuration file
350
        if ($debugMode = $input->getOption(InputOptionKeys::DEBUG_MODE)) {
351
            $instance->setDebugMode($instance->mapBoolean($debugMode));
352
        }
353
354
        // query whether or not the ignore PID flag has been specified as command line
355
        // option, if yes override the value from the configuration file
356
        if ($ignorePid = $input->getOption(InputOptionKeys::IGNORE_PID)) {
357
            $instance->setIgnorePid($instance->mapBoolean($ignorePid));
358
        }
359
360
        // query whether or not the log level has been specified as command line
361
        // option, if yes override the value from the configuration file
362
        if ($logLevel = $input->getOption(InputOptionKeys::LOG_LEVEL)) {
363
            $instance->setLogLevel($logLevel);
364
        }
365
366
        // extend the subjects with the parent configuration instance
367
        /** @var \TechDivision\Import\Cli\Configuration\Subject $subject */
368
        foreach ($instance->getSubjects() as $subject) {
369
            // set the configuration instance on the subject
370
            $subject->setConfiguration($instance);
371
        }
372
373
        // query whether or not the debug mode is enabled and log level
374
        // has NOT been overwritten with a commandline option
375
        if ($instance->isDebugMode() && !$input->getOption(InputOptionKeys::LOG_LEVEL)) {
376
            // set debug log level, if log level has NOT been overwritten on command line
377
            $instance->setLogLevel(LogLevel::DEBUG);
378
        }
379
380
        // return the initialized configuration instance
381
        return $instance;
382
    }
383
384
    /**
385
     * Return's the array with the subjects of the operation to use.
386
     *
387
     * @return \Doctrine\Common\Collections\ArrayCollection The ArrayCollection with the subjects
388
     * @throws \Exception Is thrown, if no subjects are available for the actual operation
389
     */
390
    public function getSubjects()
391
    {
392
393
        // iterate over the operations and return the subjects of the actual one
394
        /** @var TechDivision\Import\Configuration\OperationInterface $operation */
395
        foreach ($this->getOperations() as $operation) {
396
            if ($this->getOperation()->equals($operation)) {
397
                return $operation->getSubjects();
398
            }
399
        }
400
401
        // throw an exception if no subjects are available
402
        throw new \Exception(sprintf('Can\'t find any subjects for operation %s', $this->getOperation()));
403
    }
404
405
    /**
406
     * Map's the passed value to a boolean.
407
     *
408
     * @param string $value The value to map
409
     *
410
     * @return boolean The mapped value
411
     * @throws \Exception Is thrown, if the value can't be mapped
412
     */
413
    public function mapBoolean($value)
414
    {
415
416
        // try to map the passed value to a boolean
417
        if (isset($this->booleanMapping[$value])) {
418
            return $this->booleanMapping[$value];
419
        }
420
421
        // throw an exception if we can't convert the passed value
422
        throw new \Exception(sprintf('Can\'t convert %s to boolean', $value));
423
    }
424
425
    /**
426
     * Return's the operation, initialize from the actual operation name.
427
     *
428
     * @return \TechDivision\Import\Configuration\OperationInterface The operation instance
429
     */
430
    protected function getOperation()
431
    {
432
        return new Operation($this->getOperationName());
433
    }
434
435
    /**
436
     * Return's the operation name that has to be used.
437
     *
438
     * @param string $operationName The operation name that has to be used
439
     *
440
     * @return void
441
     */
442
    public function setOperationName($operationName)
443
    {
444
        return $this->operationName = $operationName;
445
    }
446
447
    /**
448
     * Return's the operation name that has to be used.
449
     *
450
     * @return string The operation name that has to be used
451
     */
452
    public function getOperationName()
453
    {
454
        return $this->operationName;
455
    }
456
457
    /**
458
     * Set's the Magento installation directory.
459
     *
460
     * @param string $installationDir The Magento installation directory
461
     *
462
     * @return void
463
     */
464
    public function setInstallationDir($installationDir)
465
    {
466
        $this->installationDir = $installationDir;
467
    }
468
469
    /**
470
     * Return's the Magento installation directory.
471
     *
472
     * @return string The Magento installation directory
473
     */
474
    public function getInstallationDir()
475
    {
476
        return $this->installationDir;
477
    }
478
479
    /**
480
     * Set's the source directory that has to be watched for new files.
481
     *
482
     * @param string $sourceDir The source directory
483
     *
484
     * @return void
485
     */
486
    public function setSourceDir($sourceDir)
487
    {
488
        $this->sourceDir = $sourceDir;
489
    }
490
491
    /**
492
     * Return's the source directory that has to be watched for new files.
493
     *
494
     * @return string The source directory
495
     */
496
    public function getSourceDir()
497
    {
498
        return $this->sourceDir;
499
    }
500
501
    /**
502
     * Return's the target directory with the files that has been imported.
503
     *
504
     * @return string The target directory
505
     */
506
    public function getTargetDir()
507
    {
508
        return $this->targetDir;
509
    }
510
511
    /**
512
     * Set's the target directory with the files that has been imported.
513
     *
514
     * @param string $targetDir The target directory
515
     *
516
     * @return void
517
     */
518
    public function setTargetDir($targetDir)
519
    {
520
        $this->targetDir = $targetDir;
521
    }
522
523
    /**
524
     * Return's the utility class with the SQL statements to use.
525
     *
526
     * @param string $utilityClassName The utility class name
527
     *
528
     * @return void
529
     */
530
    public function setUtilityClassName($utilityClassName)
531
    {
532
        return $this->utilityClassName = $utilityClassName;
533
    }
534
535
    /**
536
     * Return's the utility class with the SQL statements to use.
537
     *
538
     * @return string The utility class name
539
     */
540
    public function getUtilityClassName()
541
    {
542
        return $this->utilityClassName;
543
    }
544
545
    /**
546
     * Set's the Magento edition, EE or CE.
547
     *
548
     * @param string $magentoEdition The Magento edition
549
     *
550
     * @return void
551
     */
552
    public function setMagentoEdition($magentoEdition)
553
    {
554
        $this->magentoEdition = $magentoEdition;
555
    }
556
557
    /**
558
     * Return's the Magento edition, EE or CE.
559
     *
560
     * @return string The Magento edition
561
     */
562
    public function getMagentoEdition()
563
    {
564
        return $this->magentoEdition;
565
    }
566
567
    /**
568
     * Return's the Magento version, e. g. 2.1.0.
569
     *
570
     * @param string $magentoVersion The Magento version
571
     *
572
     * @return void
573
     */
574
    public function setMagentoVersion($magentoVersion)
575
    {
576
        $this->magentoVersion = $magentoVersion;
577
    }
578
579
    /**
580
     * Return's the Magento version, e. g. 2.1.0.
581
     *
582
     * @return string The Magento version
583
     */
584
    public function getMagentoVersion()
585
    {
586
        return $this->magentoVersion;
587
    }
588
589
    /**
590
     * Return's the subject's source date format to use.
591
     *
592
     * @return string The source date format
593
     */
594
    public function getSourceDateFormat()
595
    {
596
        return $this->sourceDateFormat;
597
    }
598
599
    /**
600
     * Set's the subject's source date format to use.
601
     *
602
     * @param string $sourceDateFormat The source date format
603
     *
604
     * @return void
605
     */
606
    public function setSourceDateFormat($sourceDateFormat)
607
    {
608
        $this->sourceDateFormat = $sourceDateFormat;
609
    }
610
611
    /**
612
     * Return's the multiple field delimiter character to use, default value is comma (,).
613
     *
614
     * @return string The multiple field delimiter character
615
     */
616
    public function getMultipleFieldDelimiter()
617
    {
618
        return $this->multipleFieldDelimiter;
619
    }
620
621
    /**
622
     * Return's the delimiter character to use, default value is comma (,).
623
     *
624
     * @return string The delimiter character
625
     */
626
    public function getDelimiter()
627
    {
628
        return $this->delimiter;
629
    }
630
631
    /**
632
     * The enclosure character to use, default value is double quotation (").
633
     *
634
     * @return string The enclosure character
635
     */
636
    public function getEnclosure()
637
    {
638
        return $this->enclosure;
639
    }
640
641
    /**
642
     * The escape character to use, default value is backslash (\).
643
     *
644
     * @return string The escape character
645
     */
646
    public function getEscape()
647
    {
648
        return $this->escape;
649
    }
650
651
    /**
652
     * The file encoding of the CSV source file, default value is UTF-8.
653
     *
654
     * @return string The charset used by the CSV source file
655
     */
656
    public function getFromCharset()
657
    {
658
        return $this->fromCharset;
659
    }
660
661
    /**
662
     * The file encoding of the CSV targetfile, default value is UTF-8.
663
     *
664
     * @return string The charset used by the CSV target file
665
     */
666
    public function getToCharset()
667
    {
668
        return $this->toCharset;
669
    }
670
671
    /**
672
     * The file mode of the CSV target file, either one of write or append, default is write.
673
     *
674
     * @return string The file mode of the CSV target file
675
     */
676
    public function getFileMode()
677
    {
678
        return $this->fileMode;
679
    }
680
681
    /**
682
     * Queries whether or not strict mode is enabled or not, default is TRUE.
683
     *
684
     * @return boolean TRUE if strict mode is enabled, else FALSE
685
     */
686
    public function isStrictMode()
687
    {
688
        return $this->strictMode;
689
    }
690
691
    /**
692
     * Return's the database configuration.
693
     *
694
     * @return \TechDivision\Import\Cli\Configuration\Database The database configuration
695
     */
696
    public function getDatabase()
697
    {
698
        return $this->database;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->database; (TechDivision\Import\Cli\...\Configuration\Database) is incompatible with the return type declared by the interface TechDivision\Import\Conf...nInterface::getDatabase of type TechDivision\Import\Tech...\Configuration\Database.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
699
    }
700
701
    /**
702
     * Return's the ArrayCollection with the configured operations.
703
     *
704
     * @return \Doctrine\Common\Collections\ArrayCollection The ArrayCollection with the operations
705
     */
706
    public function getOperations()
707
    {
708
        return $this->operations;
709
    }
710
711
    /**
712
     * Return's the TRUE if the import artefacts have to be archived.
713
     *
714
     * @return boolean TRUE if the import artefacts have to be archived
715
     */
716
    public function haveArchiveArtefacts()
717
    {
718
        return $this->archiveArtefacts;
719
    }
720
721
    /**
722
     * The directory where the archives will be stored.
723
     *
724
     * @return string The archive directory
725
     */
726
    public function getArchiveDir()
727
    {
728
        return $this->archiveDir;
729
    }
730
731
    /**
732
     * Set's the debug mode.
733
     *
734
     * @param boolean $debugMode TRUE if debug mode is enabled, else FALSE
735
     *
736
     * @return void
737
     */
738
    public function setDebugMode($debugMode)
739
    {
740
        $this->debugMode = $debugMode;
741
    }
742
743
    /**
744
     * Queries whether or not debug mode is enabled or not, default is TRUE.
745
     *
746
     * @return boolean TRUE if debug mode is enabled, else FALSE
747
     */
748
    public function isDebugMode()
749
    {
750
        return $this->debugMode;
751
    }
752
753
    /**
754
     * Set's the flag to signal that the an existing PID hast to be ignored, whether a
755
     * import process is running or not.
756
     *
757
     * @param boolean $ignorePid TRUE if the PID has to be ignored, else FALSE
758
     *
759
     * @return void
760
     */
761
    public function setIgnorePid($ignorePid)
762
    {
763
        $this->ignorePid = $ignorePid;
764
    }
765
766
    /**
767
     * Queries whether or not that the an existing PID has to be ignored.
768
     *
769
     * @return boolean TRUE if an existing PID has to be ignored, else FALSE
770
     */
771
    public function isIgnorePid()
772
    {
773
        return $this->ignorePid;
774
    }
775
776
    /**
777
     * Set's the log level to use.
778
     *
779
     * @param string $logLevel The log level to use
780
     *
781
     * @return void
782
     */
783
    public function setLogLevel($logLevel)
784
    {
785
        $this->logLevel = $logLevel;
786
    }
787
788
    /**
789
     * Return's the log level to use.
790
     *
791
     * @return string The log level to use
792
     */
793
    public function getLogLevel()
794
    {
795
        return $this->logLevel;
796
    }
797
}
798