Completed
Push — master ( 5297cb...f9fc64 )
by Jeroen
16:27 queued 02:56
created

KunstmaanGenerateCommand::isSymfony4()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
    private function setInputAndOutput(InputInterface $input, OutputInterface $output)
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)
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% 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...
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(
447
                            'Field "%s" is already defined in the parent class',
448
                            $name
449
                        )
450
                        );
451
                    }
452
453
                    // The fields cannot exist already
454
                    if (isset($fields[$name])) {
455
                        throw new \InvalidArgumentException(sprintf('Field "%s" is already defined', $name));
456
                    }
457
458
                    // Check reserved words
459
                    if ($generator->isReservedKeyword($name)) {
460
                        throw new \InvalidArgumentException(sprintf('Name "%s" is a reserved word', $name));
461
                    }
462
463
                    // Only accept a-z
464
                    if (!preg_match('/^[a-zA-Z][a-zA-Z_0-9]+$/', $name) && $name != '') {
465
                        throw new \InvalidArgumentException(sprintf('Name "%s" is invalid', $name));
466
                    }
467
468
                    return $name;
469
                }
470
            );
471
472
            // When <return> is entered
473
            if (!$fieldName) {
474
                break;
475
            }
476
477
            $typeId = $this->assistant->askSelect('Field type', $typeSelect);
478
479
            // If single -or multipe entity reference in chosen, we need to ask for the entity name
480
            if (in_array($typeStrings[$typeId], ['single_ref', 'multi_ref'])) {
481
                $bundleName = $bundle->getName();
482
                $egName = $this->isSymfony4() ? 'App\Entity\FaqItem' : "$bundleName:FaqItem, $bundleName:Blog/Comment";
483
                $question = sprintf('Reference entity name (eg. %s)', $egName);
484
                $name = $this->assistant->askAndValidate(
485
                    $question,
486
                    function ($name) use ($generator, $container) {
487
                        /*
488
                         * Replace slash to backslash. Eg: CmsBundle:Blog/Comment --> CmsBundle:Blog\Comment
489
                         *
490
                         * @see \Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory::getMetadataFor()
491
                         * @see \Doctrine\ORM\Mapping\ClassMetadataFactory::getFqcnFromAlias()
492
                         */
493
                        if (!$this->isSymfony4()) {
494
                            $name = strtr($name, '/', '\\');
495
496
                            $parts = explode(':', $name);
497
498
                            // Should contain colon
499
                            if (count($parts) != 2) {
500
                                throw new \InvalidArgumentException(sprintf('"%s" is an invalid entity name', $name));
501
                            }
502
                        } else {
503
                            $parts = explode('\\', $name);
504
                        }
505
506
                        // Check reserved words
507
                        if ($generator->isReservedKeyword(end($parts))) {
508
                            throw new \InvalidArgumentException(sprintf('"%s" contains a reserved word', $name));
509
                        }
510
511
                        $em = $container->get('doctrine')->getManager();
512
513
                        try {
514
                            $em->getClassMetadata($name);
515
                        } catch (\Exception $e) {
516
                            throw new \InvalidArgumentException(sprintf('Entity "%s" not found', $name));
517
                        }
518
519
                        return $name;
520
                    },
521
                    null,
522
                    [$bundleName]
523
                );
524
525
                $extra = $name;
526
            } else {
527
                $extra = null;
528
            }
529
530
            // If image type, force image media filter
531
            if ($typeStrings[$typeId] == 'image') {
532
                $extra = 'image';
533
            }
534
535
            // If media type, ask for media filter
536
            if ($typeStrings[$typeId] == 'media') {
537
                $mediaTypeId = $this->assistant->askSelect('Media filter', $mediaTypeSelect);
538
                $extra = strtolower($mediaTypeSelect[$mediaTypeId]);
539
            }
540
541
            if ($typeStrings[$typeId] == 'image' || $typeStrings[$typeId] == 'media') {
542
                // Ask the allowed mimetypes for the media ojbect
543
                $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',
544
                    null
545
                );
546
                if (isset($mimeTypes)) {
547
                    $mimeTypes = explode(',', $mimeTypes);
548
                }
549
                $data = [
550
                    'name' => $fieldName,
551
                    'type' => $typeStrings[$typeId],
552
                    'extra' => $extra,
553
                    'mimeTypes' => $mimeTypes,
554
                    'minHeight' => null,
555
                    'maxHeight' => null,
556
                    'minWidth' => null,
557
                    'maxWidth' => null,
558
                ];
559
560
                if ($extra == 'image') {
561
                    $minHeight = $maxHeight = $minWidth = $maxWidth = null;
562
                    if ($this->assistant->askConfirmation('Do you want to add validation of the dimensions of the media object? (y/n)',
563
                        'n',
564
                        '?',
565
                        false
566
                    )) {
567
                        // Ask the minimum height allowed for the image
568
                        $lengthValidation = function ($length) {
569
                            if ((is_numeric($length) && $length < 0) || (!is_numeric($length) && !empty($length))) {
570
                                throw new \InvalidArgumentException(sprintf('"%s" is not a valid length', $length));
571
                            } else {
572
                                return $length;
573
                            }
574
                        };
575
576
                        $minHeight = $this->assistant->askAndValidate('What is the minimum height for the media object? (in pixels)',
577
                            $lengthValidation
578
                        );
579
580
                        // Ask the maximum height allowed for the image
581
                        $maxHeight = $this->assistant->askAndValidate('What is the maximum height for the media object? (in pixels)',
582
                            $lengthValidation
583
                        );
584
585
                        // Ask the minimum width allowed for the image
586
                        $minWidth = $this->assistant->askAndValidate('What is the minimum width for the media object? (in pixels)',
587
                            $lengthValidation
588
                        );
589
590
                        //Ask the maximum width allowed for the image
591
                        $maxWidth = $this->assistant->askAndValidate('What is the maximum width for the media object? (in pixels)',
592
                            $lengthValidation
593
                        );
594
                    }
595
                    $data = [
596
                        'name' => $fieldName,
597
                        'type' => 'image',
598
                        'extra' => $extra,
599
                        'minHeight' => $minHeight,
600
                        'maxHeight' => $maxHeight,
601
                        'minWidth' => $minWidth,
602
                        'maxWidth' => $maxWidth,
603
                        'mimeTypes' => $mimeTypes,
604
                    ];
605
                }
606
            } else {
607
                $data = [
608
                    'name' => $fieldName,
609
                    'type' => $typeStrings[$typeId],
610
                    'extra' => $extra,
611
                    'minHeight' => null,
612
                    'maxHeight' => null,
613
                    'minWidth' => null,
614
                    'maxWidth' => null,
615
                    'mimeTypes' => null,
616
                ];
617
            }
618
619
            $fields[$fieldName] = $data;
620
        }
621
622
        return $fields;
623
    }
624
625
    /**
626
     * Get all the available types.
627
     *
628
     * @param bool $niceNames
629
     *
630
     * @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...
631
     */
632
    private function getTypes($niceNames = false)
633
    {
634
        $counter = 1;
635
636
        $types = [];
637
        $types[$counter++] = $niceNames ? 'Single line text' : 'single_line';
638
        $types[$counter++] = $niceNames ? 'Multi line text' : 'multi_line';
639
        $types[$counter++] = $niceNames ? 'Wysiwyg' : 'wysiwyg';
640
        $types[$counter++] = $niceNames ? 'Link (url, text, new window)' : 'link';
641
        if ($this->isBundleAvailable('KunstmaanMediaPagePartBundle')) {
642
            $types[$counter++] = $niceNames ? 'Image (media, alt text)' : 'image';
643
            $types[$counter++] = $niceNames ? 'Media (File or Video or Slideshow)' : 'media';
644
        }
645
        $types[$counter++] = $niceNames ? 'Single entity reference' : 'single_ref';
646
        $types[$counter++] = $niceNames ? 'Multi entity reference' : 'multi_ref';
647
        $types[$counter++] = $niceNames ? 'Boolean' : 'boolean';
648
        $types[$counter++] = $niceNames ? 'Integer' : 'integer';
649
        $types[$counter++] = $niceNames ? 'Decimal number' : 'decimal';
650
        $types[$counter++] = $niceNames ? 'DateTime' : 'datetime';
651
652
        return $types;
653
    }
654
655
    /**
656
     * Get all available media types.
657
     *
658
     * @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...
659
     */
660
    private function getMediaTypes()
661
    {
662
        $counter = 1;
663
664
        $types = [];
665
        $types[$counter++] = 'None';
666
        $types[$counter++] = 'File';
667
        $types[$counter++] = 'Image';
668
        $types[$counter++] = 'Video';
669
670
        return $types;
671
    }
672
673
    /**
674
     * Get all the entity fields for a specific type.
675
     *
676
     * @param BundleInterface $bundle
677
     * @param                 $objectName
678
     * @param                 $prefix
679
     * @param                 $name
680
     * @param                 $type
681
     * @param null            $extra
682
     * @param bool            $allNullable
683
     * @param null            $minHeight
684
     * @param null            $maxHeight
685
     * @param null            $minWidth
686
     * @param null            $maxWidth
687
     * @param null            $mimeTypes
688
     *
689
     * @return array
690
     */
691
    protected function getEntityFields(
692
        BundleInterface $bundle,
693
        $objectName,
694
        $prefix,
695
        $name,
696
        $type,
697
        $extra = null,
698
        $allNullable = false,
699
        $minHeight = null,
700
        $maxHeight = null,
701
        $minWidth = null,
702
        $maxWidth = null,
703
        $mimeTypes = null
704
    ) {
705
        $fields = [];
706
        switch ($type) {
707 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...
708
                $fields[$type][] = [
709
                    'fieldName' => lcfirst(Container::camelize($name)),
710
                    'type' => 'string',
711
                    'length' => '255',
712
                    'formType' => TextType::class,
713
                    'nullable' => $allNullable,
714
                ];
715
716
                break;
717 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...
718
                $fields[$type][] = [
719
                    'fieldName' => lcfirst(Container::camelize($name)),
720
                    'type' => 'text',
721
                    'formType' => TextareaType::class,
722
                    'nullable' => $allNullable,
723
                ];
724
725
                break;
726 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...
727
                $fields[$type][] = [
728
                    'fieldName' => lcfirst(Container::camelize($name)),
729
                    'type' => 'text',
730
                    'formType' => WysiwygType::class,
731
                    'nullable' => $allNullable,
732
                ];
733
734
                break;
735
            case 'link':
736
                foreach (['url', 'text'] as $subField) {
737
                    $fields[$type][$subField] = [
738
                        'fieldName' => lcfirst(Container::camelize($name.'_'.$subField)),
739
                        'type' => 'string',
740
                        'formType' => $subField == 'url' ? URLChooserType::class : TextType::class,
741
                        'nullable' => $allNullable,
742
                    ];
743
                }
744
                $fields[$type]['new_window'] = [
745
                    'fieldName' => lcfirst(Container::camelize($name.'_new_window')),
746
                    'type' => 'boolean',
747
                    'nullable' => true,
748
                    'formType' => CheckboxType::class,
749
                ];
750
751
                break;
752
            case 'image':
753
                $fields[$type]['image'] = [
754
                    'fieldName' => lcfirst(Container::camelize($name)),
755
                    'type' => 'image',
756
                    'formType' => MediaType::class,
757
                    'mediaType' => $extra,
758
                    'minHeight' => $minHeight,
759
                    'maxHeight' => $maxHeight,
760
                    'minWidth' => $minWidth,
761
                    'maxWidth' => $maxWidth,
762
                    'mimeTypes' => $mimeTypes,
763
                    'targetEntity' => 'Kunstmaan\MediaBundle\Entity\Media',
764
                    'joinColumn' => [
765
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
766
                        'referencedColumnName' => 'id',
767
                    ],
768
                    'nullable' => $allNullable,
769
                ];
770
                $fields[$type]['alt_text'] = [
771
                    'fieldName' => lcfirst(Container::camelize($name.'_alt_text')),
772
                    'type' => 'text',
773
                    'nullable' => true,
774
                    'formType' => TextType::class,
775
                ];
776
777
                break;
778
            case 'media':
779
                $fields[$type][] = [
780
                    'fieldName' => lcfirst(Container::camelize($name)),
781
                    'type' => 'media',
782
                    'formType' => MediaType::class,
783
                    'mediaType' => $extra,
784
                    'mimeTypes' => $mimeTypes,
785
                    'targetEntity' => 'Kunstmaan\MediaBundle\Entity\Media',
786
                    'joinColumn' => [
787
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
788
                        'referencedColumnName' => 'id',
789
                    ],
790
                    'nullable' => $allNullable,
791
                ];
792
793
                break;
794
            case 'single_ref':
795
                $em = $this->getContainer()->get('doctrine')->getManager();
796
                $entityName = $em->getClassMetadata($extra)->getName();
797
                $fields[$type][] = [
798
                    'fieldName' => lcfirst(Container::camelize($name)),
799
                    'type' => 'entity',
800
                    'formType' => EntityType::class,
801
                    'targetEntity' => $entityName,
802
                    'joinColumn' => [
803
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
804
                        'referencedColumnName' => 'id',
805
                    ],
806
                    'nullable' => $allNullable,
807
                ];
808
809
                break;
810
            case 'multi_ref':
811
                $em = $this->getContainer()->get('doctrine')->getManager();
812
                $entityName = $em->getClassMetadata($extra)->getName();
813
                $parts = explode('\\', $entityName);
814
                $joinTableName = strtolower(
815
                    $prefix.Container::underscore($objectName).'_'.Container::underscore(
816
                        $parts[count($parts) - 1]
817
                    )
818
                );
819
                $fields[$type][] = [
820
                    'fieldName' => lcfirst(Container::camelize($name)),
821
                    'type' => 'entity',
822
                    'formType' => EntityType::class,
823
                    'targetEntity' => $entityName,
824
                    'joinTable' => [
825
                        'name' => $joinTableName,
826
                        'joinColumns' => [
827
                            [
828
                                'name' => strtolower(Container::underscore($objectName)).'_id',
829
                                'referencedColumnName' => 'id',
830
                            ],
831
                        ],
832
                        'inverseJoinColumns' => [
833
                            [
834
                                'name' => strtolower(
835
                                        Container::underscore($parts[count($parts) - 1])
836
                                    ).'_id',
837
                                'referencedColumnName' => 'id',
838
                                'unique' => true,
839
                            ],
840
                        ],
841
                    ],
842
                    'nullable' => $allNullable,
843
                ];
844
845
                break;
846 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...
847
                $fields[$type][] = [
848
                    'fieldName' => lcfirst(Container::camelize($name)),
849
                    'type' => 'boolean',
850
                    'formType' => CheckboxType::class,
851
                    'nullable' => $allNullable,
852
                ];
853
854
                break;
855 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...
856
                $fields[$type][] = [
857
                    'fieldName' => lcfirst(Container::camelize($name)),
858
                    'type' => 'integer',
859
                    'formType' => IntegerType::class,
860
                    'nullable' => $allNullable,
861
                ];
862
863
                break;
864 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...
865
                $fields[$type][] = [
866
                    'fieldName' => lcfirst(Container::camelize($name)),
867
                    'type' => 'decimal',
868
                    'precision' => 10,
869
                    'scale' => 2,
870
                    'formType' => NumberType::class,
871
                    'nullable' => $allNullable,
872
                ];
873
874
                break;
875 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...
876
                $fields[$type][] = [
877
                    'fieldName' => lcfirst(Container::camelize($name)),
878
                    'type' => 'datetime',
879
                    'formType' => DateTimeType::class,
880
                    'nullable' => $allNullable,
881
                ];
882
883
                break;
884
        }
885
886
        return $fields;
887
    }
888
889
    /**
890
     * Get an array with the available page templates.
891
     *
892
     * @param BundleInterface $bundle The bundle for which we want to get the template configurations
893
     *
894
     * @return array
895
     */
896
    protected function getAvailableTemplates(BundleInterface $bundle)
897
    {
898
        $configs = [];
899
        $counter = 1;
900
901
        // Get the available sections from disc
902
        if (Kernel::VERSION_ID >= 40000) {
903
            $dir = $this->getContainer()->getParameter('kernel.project_dir').'/config/kunstmaancms/pagetemplates/';
904
        } else {
905
            $dir = $bundle->getPath().'/Resources/config/pagetemplates/';
906
        }
907
908
        if (file_exists($dir) && is_dir($dir)) {
909
            $finder = new Finder();
910
            $finder->files()->in($dir)->depth('== 0');
911
            foreach ($finder as $file) {
912
                $info = $this->getTemplateInfo($dir, $file->getFileName());
913
                if (is_array($info)) {
914
                    $configs[$counter++] = $info;
915
                }
916
            }
917
        }
918
919
        return $configs;
920
    }
921
922
    /**
923
     * Get the information about a pagepart section configuration file.
924
     *
925
     * @param string $dir
926
     * @param string $file
927
     *
928
     * @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...
929
     */
930
    protected function getTemplateInfo($dir, $file)
931
    {
932
        $info = null;
933
934
        try {
935
            $data = Yaml::parse(file_get_contents($dir.$file));
936
937 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...
938
                //Get rid of the bundle config lines
939
                $data = array_values(array_values(array_values($data)[0])[0])[0];
940
            }
941
942
            // Parse contexts
943
            $contexts = [];
944
            foreach ($data['rows'] as $row) {
945
                foreach ($row['regions'] as $region) {
946
                    $contexts[] = $region['name'];
947
                }
948
            }
949
            $info = [
950
                'name' => $data['name'],
951
                'contexts' => $contexts,
952
                'file' => $file,
953
            ];
954
        } catch (ParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
955
        }
956
957
        return $info;
958
    }
959
960
    /**
961
     * Get an array with the available page templates.
962
     *
963
     * @param BundleInterface $bundle The bundle for which we want to get the template configurations
964
     *
965
     * @return array
966
     */
967
    protected function getAvailablePages(BundleInterface $bundle)
968
    {
969
        $pages = [];
970
        $counter = 1;
971
972
        // Get the available pages from disc
973
        $dir = $bundle->getPath().'/Entity/Pages/';
974
        if (file_exists($dir) && is_dir($dir)) {
975
            $finder = new Finder();
976
            $finder->files()->in($dir)->depth('== 0');
977
            foreach ($finder as $file) {
978
                $pages[$counter++] = [
979
                    'name' => substr($file->getFileName(), 0, strlen($file->getFileName()) - 4),
980
                    'path' => $file->getPathName(),
981
                ];
982
            }
983
        }
984
985
        return $pages;
986
    }
987
988
    /**
989
     * Check that it is possible to generate the behat tests.
990
     *
991
     * @param BundleInterface $bundle
992
     *
993
     * @return bool
994
     */
995
    protected function canGenerateBehatTests(BundleInterface $bundle)
996
    {
997
        $behatFile = dirname($this->getContainer()->getParameter('kernel.root_dir').'/').'/behat.yml';
998
        $pagePartContext = $bundle->getPath().'/Features/Context/PagePartContext.php';
999
        $behatTestPage = $bundle->getPath().'/Entity/Pages/BehatTestPage.php';
1000
1001
        // Make sure behat is configured and the PagePartContext and BehatTestPage exits
1002
        return file_exists($behatFile) && file_exists($pagePartContext) && file_exists($behatTestPage);
1003
    }
1004
1005
    /**
1006
     * @internal
1007
     */
1008
    protected function isSymfony4()
1009
    {
1010
        return Kernel::VERSION_ID >= 40000;
1011
    }
1012
}
1013