KunstmaanGenerateCommand::askForPrefix()   B
last analyzed

Complexity

Conditions 8
Paths 10

Size

Total Lines 43

Duplication

Lines 5
Ratio 11.63 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 5
loc 43
ccs 0
cts 35
cp 0
rs 7.9875
c 0
b 0
f 0
cc 8
nc 10
nop 2
crap 72
1
<?php
2
3
namespace Kunstmaan\GeneratorBundle\Command;
4
5
use Kunstmaan\AdminBundle\Form\WysiwygType;
6
use Kunstmaan\GeneratorBundle\Helper\CommandAssistant;
7
use Kunstmaan\GeneratorBundle\Helper\GeneratorUtils;
8
use Kunstmaan\GeneratorBundle\Helper\Sf4AppBundle;
9
use Kunstmaan\MediaBundle\Form\Type\MediaType;
10
use Kunstmaan\NodeBundle\Form\Type\URLChooserType;
11
use Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineCommand;
12
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\DependencyInjection\Container;
16
use Symfony\Component\Finder\Finder;
17
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
18
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
19
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
20
use Symfony\Component\Form\Extension\Core\Type\NumberType;
21
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
22
use Symfony\Component\Form\Extension\Core\Type\TextType;
23
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
24
use Symfony\Component\HttpKernel\Kernel;
25
use Symfony\Component\Yaml\Exception\ParseException;
26
use Symfony\Component\Yaml\Yaml;
27
28
abstract class KunstmaanGenerateCommand extends GenerateDoctrineCommand
29
{
30
    /**
31
     * @var CommandAssistant
32
     */
33
    protected $assistant;
34
35
    /**
36
     * Interacts with the user.
37
     *
38
     * @param InputInterface  $input  An InputInterface instance
39
     * @param OutputInterface $output An OutputInterface instance
40
     */
41
    protected function interact(InputInterface $input, OutputInterface $output)
42
    {
43
        $this->setInputAndOutput($input, $output);
44
45
        $welcomeText = $this->getWelcomeText();
46
        if (!empty($welcomeText)) {
47
            $this->assistant->writeSection($this->getWelcomeText());
48
        }
49
50
        $this->doInteract();
51
    }
52
53
    /**
54
     * @param InputInterface  $input
55
     * @param OutputInterface $output
56
     *
57
     * @return int|null
58
     */
59
    protected function execute(InputInterface $input, OutputInterface $output)
60
    {
61
        $this->setInputAndOutput($input, $output);
62
63
        return $this->doExecute();
64
    }
65
66
    /**
67
     * Create the CommandAssistant.
68
     *
69
     * @param InputInterface  $input
70
     * @param OutputInterface $output
71
     */
72 View Code Duplication
    private function setInputAndOutput(InputInterface $input, OutputInterface $output)
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...
73
    {
74
        if (is_null($this->assistant)) {
75
            $this->assistant = new CommandAssistant();
76
            $this->assistant->setQuestionHelper($this->getQuestionHelper());
77
            $this->assistant->setKernel($this->getApplication()->getKernel());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Console\Application as the method getKernel() does only exist in the following sub-classes of Symfony\Component\Console\Application: Symfony\Bundle\FrameworkBundle\Console\Application. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
78
        }
79
        $this->assistant->setOutput($output);
80
        $this->assistant->setInput($input);
81
    }
82
83
    /**
84
     * Do the interaction with the end user.
85
     */
86
    abstract protected function doInteract();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
87
88
    /**
89
     * This function implements the final execution of the Generator.
90
     * It calls the execute function with the correct parameters.
91
     */
92
    abstract protected function doExecute();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
93
94
    /**
95
     * The text to be displayed on top of the generator.
96
     *
97
     * @return string|array
98
     */
99
    abstract protected function getWelcomeText();
100
101
    /**
102
     * Get an array with all the bundles the user has created.
103
     *
104
     * @return array
105
     */
106
    protected function getOwnBundles()
107
    {
108
        $bundles = [];
109
        $counter = 1;
110
        $dir = dirname($this->getContainer()->getParameter('kernel.root_dir').'/').'/src/';
111
        $finder = new Finder();
112
        $finder->in($dir)->name('*Bundle.php');
113
114
        foreach ($finder as $file) {
115
            $bundles[$counter++] = [
116
                'name' => basename($file->getFilename(), '.php'),
117
                'namespace' => $file->getRelativePath(),
118
                'dir' => $file->getPath(),
119
            ];
120
        }
121
122
        return $bundles;
123
    }
124
125
    /**
126
     * Check that a bundle is available (loaded in AppKernel)
127
     *
128
     * @param string $bundleName
129
     *
130
     * @return bool
131
     */
132
    protected function isBundleAvailable($bundleName)
133
    {
134
        $allBundles = array_keys($this->assistant->getKernel()->getBundles());
135
136
        return in_array($bundleName, $allBundles);
137
    }
138
139
    /**
140
     * Asks for the prefix and sets it on the InputInterface as the 'prefix' option, if this option is not set yet.
141
     * Will set the default to a snake_cased namespace when the namespace has been set on the InputInterface.
142
     *
143
     * @param array  $text      What you want printed before the prefix is asked. If null is provided it'll write a default text.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $text not be null|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
144
     * @param string $namespace An optional namespace. If this is set it'll create the default based on this prefix.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $namespace not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
145
     *                          If it's not provided it'll check if the InputInterface already has the namespace option.
146
     *
147
     * @return string The prefix. But it's also been set on the InputInterface.
148
     */
149
    protected function askForPrefix(array $text = null, $namespace = null)
150
    {
151
        $prefix = $this->assistant->getOptionOrDefault('prefix', null);
152
153
        if (is_null($text)) {
154
            $text = [
155
                'You can add a prefix to the table names of the generated entities for example: '.
156
                '<comment>projectname_bundlename_</comment>',
157
                'Enter an underscore \'_\' if you don\'t want a prefix.',
158
                '',
159
            ];
160
        }
161
162
        while (is_null($prefix)) {
163
            if (count($text) > 0) {
164
                $this->assistant->writeLine($text);
165
            }
166
167
            if (is_null($namespace) || empty($namespace)) {
168
                $namespace = $this->assistant->getOption('namespace');
169
            } else {
170
                $namespace = $this->fixNamespace($namespace);
171
            }
172
            $defaultPrefix = GeneratorUtils::cleanPrefix($this->convertNamespaceToSnakeCase($namespace));
173
            $prefix = GeneratorUtils::cleanPrefix($this->assistant->ask('Tablename prefix', $defaultPrefix));
174
175
            if ($prefix == '') {
176
                break;
177
            }
178
179
            $output = $this->assistant->getOutput();
180 View Code Duplication
            if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $prefix)) {
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...
181
                $output->writeln(sprintf('<bg=red> "%s" contains invalid characters</>', $prefix));
182
                $prefix = $text = null;
183
184
                continue;
185
            }
186
187
            $this->assistant->setOption('prefix', $prefix);
188
        }
189
190
        return $prefix;
191
    }
192
193
    /**
194
     * Converts something like Namespace\BundleNameBundle to namespace_bundlenamebundle.
195
     *
196
     * @param string $namespace
197
     *
198
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
199
     */
200 View Code Duplication
    private function convertNamespaceToSnakeCase($namespace)
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...
201
    {
202
        if (is_null($namespace)) {
203
            return null;
204
        }
205
206
        return str_replace('/', '_', strtolower($this->fixNamespace($namespace)));
207
    }
208
209
    /**
210
     * Replaces '\' with '/'.
211
     *
212
     * @param $namespace
213
     *
214
     * @return mixed
215
     */
216
    private function fixNamespace($namespace)
217
    {
218
        return str_replace('\\', '/', $namespace);
219
    }
220
221
    /**
222
     * Ask for which bundle we need to generate something. It there is only one custom bundle
223
     * created by the user, we don't ask anything and just use that bundle. If the user provided
224
     * a namespace as input option, we try to get that bundle first.
225
     *
226
     * @param string      $objectName          The thing we are going to create (pagepart, bundle, layout, ...)
227
     * @param string|null $namespace           The namespace provided as input option
228
     * @param string      $questionMoreBundles
229
     * @param string      $questionOneBundle
230
     *
231
     * @return BundleInterface
232
     */
233
    protected function askForBundleName(
234
        $objectName,
235
        $namespace = null,
236
        $questionMoreBundles = "\nIn which bundle do you want to create the %s",
237
        $questionOneBundle = "The %s will be created for the <comment>%s</comment> bundle.\n"
238
    ) {
239
        if (Kernel::VERSION_ID >= 40000) {
240
            return new Sf4AppBundle($this->getContainer()->getParameter('kernel.project_dir'));
241
        }
242
243
        $ownBundles = $this->getOwnBundles();
244
        if (count($ownBundles) <= 0) {
245
            $this->assistant->writeError("Looks like you don't have created any bundles for your project...", true);
246
        }
247
248
        // If the user provided the namespace as input option
249
        if (!is_null($namespace)) {
250
            foreach ($ownBundles as $key => $bundleInfo) {
251
                if (GeneratorUtils::fixNamespace($namespace) == GeneratorUtils::fixNamespace(
252
                        $bundleInfo['namespace']
253
                    )
254
                ) {
255
                    $bundleName = $bundleInfo['name'];
256
257
                    break;
258
                }
259
            }
260
261
            // When the provided namespace was not found, we show an error on the screen and ask the bundle again
262
            if (empty($bundleName)) {
263
                $this->assistant->writeError("The provided bundle namespace '$namespace' was not found...");
264
            }
265
        }
266
267
        if (empty($bundleName)) {
268
            // If we only have 1 bundle, we don't need to ask
269
            if (count($ownBundles) > 1) {
270
                $bundleSelect = [];
271
                foreach ($ownBundles as $key => $bundleInfo) {
272
                    $bundleSelect[$key] = $bundleInfo['name'];
273
                }
274
                $bundleId = $this->assistant->askSelect(
275
                    sprintf($questionMoreBundles, $objectName),
276
                    $bundleSelect
277
                );
278
                $bundleName = $ownBundles[$bundleId]['name'];
279
280
                $this->assistant->writeLine('');
281
            } else {
282
                $bundleName = $ownBundles[1]['name'];
283
                $this->assistant->writeLine(
284
                    [sprintf($questionOneBundle, $objectName, $bundleName)]
285
                );
286
            }
287
        }
288
289
        $bundle = $this->assistant->getKernel()->getBundle($bundleName);
290
291
        return $bundle;
292
    }
293
294
    /**
295
     * Ask the end user to select one (or more) section configuration(s).
296
     *
297
     * @param string          $question
298
     * @param BundleInterface $bundle
299
     * @param bool            $multiple
300
     * @param string|null     $context
301
     * @param array           $defaultSections
302
     *
303
     * @return array|null
304
     */
305
    protected function askForSections(
306
        $question,
307
        BundleInterface $bundle,
308
        $multiple = false,
309
        $context = null,
310
        $defaultSections = []
311
    ) {
312
        $allSections = $this->getAvailableSections($bundle, $context, $defaultSections);
313
        $sections = [];
314
315
        // If there are more options to choose from, we ask the end user
316
        if (count($allSections) > 0) {
317
            $sectionSelect = [];
318
            foreach ($allSections as $key => $sectionInfo) {
319
                $sectionSelect[$key] = $sectionInfo['name'].' ('.$sectionInfo['file'].')';
320
            }
321
            $this->assistant->writeLine('');
322
            $sectionIds = $this->assistant->askSelect($question, $sectionSelect, null, $multiple);
323
            if (is_array($sectionIds)) {
324
                foreach ($sectionIds as $id) {
325
                    $sections[] = $allSections[$id]['file'];
326
                }
327
            } else {
328
                $sections[] = $allSections[$sectionIds]['file'];
329
            }
330
        }
331
332
        if ($multiple) {
333
            return $sections;
334
        } else {
335
            return count($sections) > 0 ? $sections[0] : null;
336
        }
337
    }
338
339
    /**
340
     * Get an array with the available page sections. We also parse the yaml files to get more information about
341
     * the sections.
342
     *
343
     * @param BundleInterface $bundle          The bundle for which we want to get the section configuration
344
     * @param string|null     $context         If provided, only return configurations with this context
345
     * @param array           $defaultSections The default section configurations that are always available
346
     *
347
     * @return array
348
     */
349
    protected function getAvailableSections(BundleInterface $bundle, $context = null, $defaultSections = [])
350
    {
351
        $configs = [];
352
        $counter = 1;
353
354
        // Get the available sections from disc
355
        $dir = Kernel::VERSION_ID >= 40000 ?
356
            $this->getContainer()->getParameter('kernel.project_dir').'/config/kunstmaancms/pageparts/' :
357
            $bundle->getPath().'/Resources/config/pageparts/'
358
        ;
359
        if (file_exists($dir) && is_dir($dir)) {
360
            $finder = new Finder();
361
            $finder->files()->in($dir)->depth('== 0');
362
            foreach ($finder as $file) {
363
                $info = $this->getSectionInfo($dir, $file->getFileName());
364
365
                if (is_array($info) && (is_null($context) || $info['context'] == $context)) {
366
                    $configs[$counter++] = $info;
367
                    if (array_key_exists($info['file'], $defaultSections)) {
368
                        unset($defaultSections[$info['file']]);
369
                    }
370
                }
371
            }
372
        }
373
374
        // Add the default sections
375
        foreach ($defaultSections as $file => $info) {
376
            if (is_null($context) || $info['context'] == $context) {
377
                $configs[$counter++] = $info;
378
            }
379
        }
380
381
        return $configs;
382
    }
383
384
    /**
385
     * Get the information about a pagepart section configuration file.
386
     *
387
     * @param string $dir
388
     * @param string $file
389
     *
390
     * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
391
     */
392
    private function getSectionInfo($dir, $file)
393
    {
394
        $info = null;
395
396
        try {
397
            $data = Yaml::parse(file_get_contents($dir.$file));
398
399 View Code Duplication
            if (array_key_exists('kunstmaan_page_part', $data)) {
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...
400
                //Get rid of the bundle config lines
401
                $data = array_values(array_values(array_values($data)[0])[0])[0];
402
            }
403
404
            $info = [
405
                'name' => $data['name'],
406
                'context' => $data['context'],
407
                'file' => $file,
408
                //'file_clean' => substr($file, 0, strlen($file)-4)
409
            ];
410
        } catch (ParseException $e) {
411
        }
412
413
        return $info;
414
    }
415
416
    /**
417
     * Get an array of fields that need to be added to the entity.
418
     *
419
     * @param BundleInterface $bundle
420
     * @param array           $reservedFields
421
     *
422
     * @return array
423
     */
424
    protected function askEntityFields(BundleInterface $bundle, array $reservedFields = ['id'])
425
    {
426
        $this->assistant->writeLine('<info>Available field types:</info> ');
427
        $typeSelect = $this->getTypes(true);
428
        foreach ($typeSelect as $type) {
429
            $this->assistant->writeLine(sprintf('<comment>- %s</comment>', $type));
430
        }
431
432
        $fields = [];
433
        $typeStrings = $this->getTypes();
434
        $mediaTypeSelect = $this->getMediaTypes();
435
        $generator = $this->getGenerator();
436
        $container = $this->getContainer();
437
438
        while (true) {
439
            $this->assistant->writeLine('');
440
441
            $fieldName = $this->assistant->askAndValidate(
442
                'New field name (press <return> to stop adding fields)',
443
                function ($name) use ($fields, $reservedFields, $generator) {
444
                    // The fields cannot exist in the reserved field list
445
                    if (in_array($name, $reservedFields)) {
446
                        throw new \InvalidArgumentException(sprintf('Field "%s" is already defined in the parent class', $name));
447
                    }
448
449
                    // The fields cannot exist already
450
                    if (isset($fields[$name])) {
451
                        throw new \InvalidArgumentException(sprintf('Field "%s" is already defined', $name));
452
                    }
453
454
                    // Check reserved words
455
                    if ($generator->isReservedKeyword($name)) {
456
                        throw new \InvalidArgumentException(sprintf('Name "%s" is a reserved word', $name));
457
                    }
458
459
                    // Only accept a-z
460
                    if (!preg_match('/^[a-zA-Z][a-zA-Z_0-9]+$/', $name) && $name != '') {
461
                        throw new \InvalidArgumentException(sprintf('Name "%s" is invalid', $name));
462
                    }
463
464
                    return $name;
465
                }
466
            );
467
468
            // When <return> is entered
469
            if (!$fieldName) {
470
                break;
471
            }
472
473
            $typeId = $this->assistant->askSelect('Field type', $typeSelect);
474
475
            // If single -or multipe entity reference in chosen, we need to ask for the entity name
476
            if (in_array($typeStrings[$typeId], ['single_ref', 'multi_ref'])) {
477
                $bundleName = $bundle->getName();
478
                $egName = $this->isSymfony4() ? 'App\Entity\FaqItem' : "$bundleName:FaqItem, $bundleName:Blog/Comment";
479
                $question = sprintf('Reference entity name (eg. %s)', $egName);
480
                $name = $this->assistant->askAndValidate(
481
                    $question,
482
                    function ($name) use ($generator, $container) {
483
                        /*
484
                         * Replace slash to backslash. Eg: CmsBundle:Blog/Comment --> CmsBundle:Blog\Comment
485
                         *
486
                         * @see \Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory::getMetadataFor()
487
                         * @see \Doctrine\ORM\Mapping\ClassMetadataFactory::getFqcnFromAlias()
488
                         */
489
                        if (!$this->isSymfony4()) {
490
                            $name = strtr($name, '/', '\\');
491
492
                            $parts = explode(':', $name);
493
494
                            // Should contain colon
495
                            if (count($parts) != 2) {
496
                                throw new \InvalidArgumentException(sprintf('"%s" is an invalid entity name', $name));
497
                            }
498
                        } else {
499
                            $parts = explode('\\', $name);
500
                        }
501
502
                        // Check reserved words
503
                        if ($generator->isReservedKeyword(end($parts))) {
504
                            throw new \InvalidArgumentException(sprintf('"%s" contains a reserved word', $name));
505
                        }
506
507
                        $em = $container->get('doctrine')->getManager();
508
509
                        try {
510
                            $em->getClassMetadata($name);
511
                        } catch (\Exception $e) {
512
                            throw new \InvalidArgumentException(sprintf('Entity "%s" not found', $name));
513
                        }
514
515
                        return $name;
516
                    },
517
                    null,
518
                    [$bundleName]
519
                );
520
521
                $extra = $name;
522
            } else {
523
                $extra = null;
524
            }
525
526
            // If image type, force image media filter
527
            if ($typeStrings[$typeId] == 'image') {
528
                $extra = 'image';
529
            }
530
531
            // If media type, ask for media filter
532
            if ($typeStrings[$typeId] == 'media') {
533
                $mediaTypeId = $this->assistant->askSelect('Media filter', $mediaTypeSelect);
534
                $extra = strtolower($mediaTypeSelect[$mediaTypeId]);
535
            }
536
537
            if ($typeStrings[$typeId] == 'image' || $typeStrings[$typeId] == 'media') {
538
                // Ask the allowed mimetypes for the media ojbect
539
                $mimeTypes = $this->assistant->ask('Do you want to limit the possible file types? Then specify a comma-seperated list of types (example: image/png,image/svg+xml), otherwise press ENTER',
540
                    null
541
                );
542
                if (isset($mimeTypes)) {
543
                    $mimeTypes = explode(',', $mimeTypes);
544
                }
545
                $data = [
546
                    'name' => $fieldName,
547
                    'type' => $typeStrings[$typeId],
548
                    'extra' => $extra,
549
                    'mimeTypes' => $mimeTypes,
550
                    'minHeight' => null,
551
                    'maxHeight' => null,
552
                    'minWidth' => null,
553
                    'maxWidth' => null,
554
                ];
555
556
                if ($extra == 'image') {
557
                    $minHeight = $maxHeight = $minWidth = $maxWidth = null;
558
                    if ($this->assistant->askConfirmation('Do you want to add validation of the dimensions of the media object? (y/n)',
559
                        'n',
560
                        '?',
561
                        false
562
                    )) {
563
                        // Ask the minimum height allowed for the image
564
                        $lengthValidation = function ($length) {
565
                            if ((is_numeric($length) && $length < 0) || (!is_numeric($length) && !empty($length))) {
566
                                throw new \InvalidArgumentException(sprintf('"%s" is not a valid length', $length));
567
                            } else {
568
                                return $length;
569
                            }
570
                        };
571
572
                        $minHeight = $this->assistant->askAndValidate('What is the minimum height for the media object? (in pixels)',
573
                            $lengthValidation
574
                        );
575
576
                        // Ask the maximum height allowed for the image
577
                        $maxHeight = $this->assistant->askAndValidate('What is the maximum height for the media object? (in pixels)',
578
                            $lengthValidation
579
                        );
580
581
                        // Ask the minimum width allowed for the image
582
                        $minWidth = $this->assistant->askAndValidate('What is the minimum width for the media object? (in pixels)',
583
                            $lengthValidation
584
                        );
585
586
                        //Ask the maximum width allowed for the image
587
                        $maxWidth = $this->assistant->askAndValidate('What is the maximum width for the media object? (in pixels)',
588
                            $lengthValidation
589
                        );
590
                    }
591
                    $data = [
592
                        'name' => $fieldName,
593
                        'type' => 'image',
594
                        'extra' => $extra,
595
                        'minHeight' => $minHeight,
596
                        'maxHeight' => $maxHeight,
597
                        'minWidth' => $minWidth,
598
                        'maxWidth' => $maxWidth,
599
                        'mimeTypes' => $mimeTypes,
600
                    ];
601
                }
602
            } else {
603
                $data = [
604
                    'name' => $fieldName,
605
                    'type' => $typeStrings[$typeId],
606
                    'extra' => $extra,
607
                    'minHeight' => null,
608
                    'maxHeight' => null,
609
                    'minWidth' => null,
610
                    'maxWidth' => null,
611
                    'mimeTypes' => null,
612
                ];
613
            }
614
615
            $fields[$fieldName] = $data;
616
        }
617
618
        return $fields;
619
    }
620
621
    /**
622
     * Get all the available types.
623
     *
624
     * @param bool $niceNames
625
     *
626
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
627
     */
628
    private function getTypes($niceNames = false)
629
    {
630
        $counter = 1;
631
632
        $types = [];
633
        $types[$counter++] = $niceNames ? 'Single line text' : 'single_line';
634
        $types[$counter++] = $niceNames ? 'Multi line text' : 'multi_line';
635
        $types[$counter++] = $niceNames ? 'Wysiwyg' : 'wysiwyg';
636
        $types[$counter++] = $niceNames ? 'Link (url, text, new window)' : 'link';
637
        if ($this->isBundleAvailable('KunstmaanMediaPagePartBundle')) {
638
            $types[$counter++] = $niceNames ? 'Image (media, alt text)' : 'image';
639
            $types[$counter++] = $niceNames ? 'Media (File or Video or Slideshow)' : 'media';
640
        }
641
        $types[$counter++] = $niceNames ? 'Single entity reference' : 'single_ref';
642
        $types[$counter++] = $niceNames ? 'Multi entity reference' : 'multi_ref';
643
        $types[$counter++] = $niceNames ? 'Boolean' : 'boolean';
644
        $types[$counter++] = $niceNames ? 'Integer' : 'integer';
645
        $types[$counter++] = $niceNames ? 'Decimal number' : 'decimal';
646
        $types[$counter++] = $niceNames ? 'DateTime' : 'datetime';
647
648
        return $types;
649
    }
650
651
    /**
652
     * Get all available media types.
653
     *
654
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
655
     */
656
    private function getMediaTypes()
657
    {
658
        $counter = 1;
659
660
        $types = [];
661
        $types[$counter++] = 'None';
662
        $types[$counter++] = 'File';
663
        $types[$counter++] = 'Image';
664
        $types[$counter++] = 'Video';
665
666
        return $types;
667
    }
668
669
    /**
670
     * Get all the entity fields for a specific type.
671
     *
672
     * @param BundleInterface $bundle
673
     * @param                 $objectName
674
     * @param                 $prefix
675
     * @param                 $name
676
     * @param                 $type
677
     * @param null            $extra
678
     * @param bool            $allNullable
679
     * @param null            $minHeight
680
     * @param null            $maxHeight
681
     * @param null            $minWidth
682
     * @param null            $maxWidth
683
     * @param null            $mimeTypes
684
     *
685
     * @return array
686
     */
687
    protected function getEntityFields(
688
        BundleInterface $bundle,
689
        $objectName,
690
        $prefix,
691
        $name,
692
        $type,
693
        $extra = null,
694
        $allNullable = false,
695
        $minHeight = null,
696
        $maxHeight = null,
697
        $minWidth = null,
698
        $maxWidth = null,
699
        $mimeTypes = null
700
    ) {
701
        $fields = [];
702
        switch ($type) {
703 View Code Duplication
            case 'single_line':
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...
704
                $fields[$type][] = [
705
                    'fieldName' => lcfirst(Container::camelize($name)),
706
                    'type' => 'string',
707
                    'length' => '255',
708
                    'formType' => TextType::class,
709
                    'nullable' => $allNullable,
710
                ];
711
712
                break;
713 View Code Duplication
            case 'multi_line':
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...
714
                $fields[$type][] = [
715
                    'fieldName' => lcfirst(Container::camelize($name)),
716
                    'type' => 'text',
717
                    'formType' => TextareaType::class,
718
                    'nullable' => $allNullable,
719
                ];
720
721
                break;
722 View Code Duplication
            case 'wysiwyg':
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...
723
                $fields[$type][] = [
724
                    'fieldName' => lcfirst(Container::camelize($name)),
725
                    'type' => 'text',
726
                    'formType' => WysiwygType::class,
727
                    'nullable' => $allNullable,
728
                ];
729
730
                break;
731
            case 'link':
732
                foreach (['url', 'text'] as $subField) {
733
                    $fields[$type][$subField] = [
734
                        'fieldName' => lcfirst(Container::camelize($name.'_'.$subField)),
735
                        'type' => 'string',
736
                        'formType' => $subField == 'url' ? URLChooserType::class : TextType::class,
737
                        'nullable' => $allNullable,
738
                    ];
739
                }
740
                $fields[$type]['new_window'] = [
741
                    'fieldName' => lcfirst(Container::camelize($name.'_new_window')),
742
                    'type' => 'boolean',
743
                    'nullable' => true,
744
                    'formType' => CheckboxType::class,
745
                ];
746
747
                break;
748
            case 'image':
749
                $fields[$type]['image'] = [
750
                    'fieldName' => lcfirst(Container::camelize($name)),
751
                    'type' => 'image',
752
                    'formType' => MediaType::class,
753
                    'mediaType' => $extra,
754
                    'minHeight' => $minHeight,
755
                    'maxHeight' => $maxHeight,
756
                    'minWidth' => $minWidth,
757
                    'maxWidth' => $maxWidth,
758
                    'mimeTypes' => $mimeTypes,
759
                    'targetEntity' => 'Kunstmaan\MediaBundle\Entity\Media',
760
                    'joinColumn' => [
761
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
762
                        'referencedColumnName' => 'id',
763
                    ],
764
                    'nullable' => $allNullable,
765
                ];
766
                $fields[$type]['alt_text'] = [
767
                    'fieldName' => lcfirst(Container::camelize($name.'_alt_text')),
768
                    'type' => 'text',
769
                    'nullable' => true,
770
                    'formType' => TextType::class,
771
                ];
772
773
                break;
774
            case 'media':
775
                $fields[$type][] = [
776
                    'fieldName' => lcfirst(Container::camelize($name)),
777
                    'type' => 'media',
778
                    'formType' => MediaType::class,
779
                    'mediaType' => $extra,
780
                    'mimeTypes' => $mimeTypes,
781
                    'targetEntity' => 'Kunstmaan\MediaBundle\Entity\Media',
782
                    'joinColumn' => [
783
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
784
                        'referencedColumnName' => 'id',
785
                    ],
786
                    'nullable' => $allNullable,
787
                ];
788
789
                break;
790
            case 'single_ref':
791
                $em = $this->getContainer()->get('doctrine')->getManager();
792
                $entityName = $em->getClassMetadata($extra)->getName();
793
                $fields[$type][] = [
794
                    'fieldName' => lcfirst(Container::camelize($name)),
795
                    'type' => 'entity',
796
                    'formType' => EntityType::class,
797
                    'targetEntity' => $entityName,
798
                    'joinColumn' => [
799
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
800
                        'referencedColumnName' => 'id',
801
                    ],
802
                    'nullable' => $allNullable,
803
                ];
804
805
                break;
806
            case 'multi_ref':
807
                $em = $this->getContainer()->get('doctrine')->getManager();
808
                $entityName = $em->getClassMetadata($extra)->getName();
809
                $parts = explode('\\', $entityName);
810
                $joinTableName = strtolower(
811
                    $prefix.Container::underscore($objectName).'_'.Container::underscore(
812
                        $parts[count($parts) - 1]
813
                    )
814
                );
815
                $fields[$type][] = [
816
                    'fieldName' => lcfirst(Container::camelize($name)),
817
                    'type' => 'entity',
818
                    'formType' => EntityType::class,
819
                    'targetEntity' => $entityName,
820
                    'joinTable' => [
821
                        'name' => $joinTableName,
822
                        'joinColumns' => [
823
                            [
824
                                'name' => strtolower(Container::underscore($objectName)).'_id',
825
                                'referencedColumnName' => 'id',
826
                            ],
827
                        ],
828
                        'inverseJoinColumns' => [
829
                            [
830
                                'name' => strtolower(
831
                                        Container::underscore($parts[count($parts) - 1])
832
                                    ).'_id',
833
                                'referencedColumnName' => 'id',
834
                                'unique' => true,
835
                            ],
836
                        ],
837
                    ],
838
                    'nullable' => $allNullable,
839
                ];
840
841
                break;
842 View Code Duplication
            case 'boolean':
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...
843
                $fields[$type][] = [
844
                    'fieldName' => lcfirst(Container::camelize($name)),
845
                    'type' => 'boolean',
846
                    'formType' => CheckboxType::class,
847
                    'nullable' => $allNullable,
848
                ];
849
850
                break;
851 View Code Duplication
            case 'integer':
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...
852
                $fields[$type][] = [
853
                    'fieldName' => lcfirst(Container::camelize($name)),
854
                    'type' => 'integer',
855
                    'formType' => IntegerType::class,
856
                    'nullable' => $allNullable,
857
                ];
858
859
                break;
860 View Code Duplication
            case 'decimal':
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...
861
                $fields[$type][] = [
862
                    'fieldName' => lcfirst(Container::camelize($name)),
863
                    'type' => 'decimal',
864
                    'precision' => 10,
865
                    'scale' => 2,
866
                    'formType' => NumberType::class,
867
                    'nullable' => $allNullable,
868
                ];
869
870
                break;
871 View Code Duplication
            case 'datetime':
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...
872
                $fields[$type][] = [
873
                    'fieldName' => lcfirst(Container::camelize($name)),
874
                    'type' => 'datetime',
875
                    'formType' => DateTimeType::class,
876
                    'nullable' => $allNullable,
877
                ];
878
879
                break;
880
        }
881
882
        return $fields;
883
    }
884
885
    /**
886
     * Get an array with the available page templates.
887
     *
888
     * @param BundleInterface $bundle The bundle for which we want to get the template configurations
889
     *
890
     * @return array
891
     */
892
    protected function getAvailableTemplates(BundleInterface $bundle)
893
    {
894
        $configs = [];
895
        $counter = 1;
896
897
        // Get the available sections from disc
898
        if (Kernel::VERSION_ID >= 40000) {
899
            $dir = $this->getContainer()->getParameter('kernel.project_dir').'/config/kunstmaancms/pagetemplates/';
900
        } else {
901
            $dir = $bundle->getPath().'/Resources/config/pagetemplates/';
902
        }
903
904
        if (file_exists($dir) && is_dir($dir)) {
905
            $finder = new Finder();
906
            $finder->files()->in($dir)->depth('== 0');
907
            foreach ($finder as $file) {
908
                $info = $this->getTemplateInfo($dir, $file->getFileName());
909
                if (is_array($info)) {
910
                    $configs[$counter++] = $info;
911
                }
912
            }
913
        }
914
915
        return $configs;
916
    }
917
918
    /**
919
     * Get the information about a pagepart section configuration file.
920
     *
921
     * @param string $dir
922
     * @param string $file
923
     *
924
     * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
925
     */
926
    protected function getTemplateInfo($dir, $file)
927
    {
928
        $info = null;
929
930
        try {
931
            $data = Yaml::parse(file_get_contents($dir.$file));
932
933 View Code Duplication
            if (array_key_exists('kunstmaan_page_part', $data)) {
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...
934
                //Get rid of the bundle config lines
935
                $data = array_values(array_values(array_values($data)[0])[0])[0];
936
            }
937
938
            // Parse contexts
939
            $contexts = [];
940
            foreach ($data['rows'] as $row) {
941
                foreach ($row['regions'] as $region) {
942
                    $contexts[] = $region['name'];
943
                }
944
            }
945
            $info = [
946
                'name' => $data['name'],
947
                'contexts' => $contexts,
948
                'file' => $file,
949
            ];
950
        } catch (ParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
951
        }
952
953
        return $info;
954
    }
955
956
    /**
957
     * Get an array with the available page templates.
958
     *
959
     * @param BundleInterface $bundle The bundle for which we want to get the template configurations
960
     *
961
     * @return array
962
     */
963
    protected function getAvailablePages(BundleInterface $bundle)
964
    {
965
        $pages = [];
966
        $counter = 1;
967
968
        // Get the available pages from disc
969
        $dir = $bundle->getPath().'/Entity/Pages/';
970
        if (file_exists($dir) && is_dir($dir)) {
971
            $finder = new Finder();
972
            $finder->files()->in($dir)->depth('== 0');
973
            foreach ($finder as $file) {
974
                $pages[$counter++] = [
975
                    'name' => substr($file->getFileName(), 0, strlen($file->getFileName()) - 4),
976
                    'path' => $file->getPathName(),
977
                ];
978
            }
979
        }
980
981
        return $pages;
982
    }
983
984
    /**
985
     * Check that it is possible to generate the behat tests.
986
     *
987
     * @param BundleInterface $bundle
988
     *
989
     * @return bool
990
     */
991
    protected function canGenerateBehatTests(BundleInterface $bundle)
992
    {
993
        $behatFile = dirname($this->getContainer()->getParameter('kernel.root_dir').'/').'/behat.yml';
994
        $pagePartContext = $bundle->getPath().'/Features/Context/PagePartContext.php';
995
        $behatTestPage = $bundle->getPath().'/Entity/Pages/BehatTestPage.php';
996
997
        // Make sure behat is configured and the PagePartContext and BehatTestPage exits
998
        return file_exists($behatFile) && file_exists($pagePartContext) && file_exists($behatTestPage);
999
    }
1000
1001
    /**
1002
     * @internal
1003
     */
1004
    protected function isSymfony4()
1005
    {
1006
        return Kernel::VERSION_ID >= 40000;
1007
    }
1008
}
1009