ThemePrepareIconsCommand::execute()   F
last analyzed

Complexity

Conditions 23
Paths 3480

Size

Total Lines 125
Code Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 78
nc 3480
nop 2
dl 0
loc 125
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Shopware\Storefront\Theme\Command;
4
5
use Shopware\Core\Framework\Log\Package;
6
use SVG\Nodes\Structures\SVGDefs;
7
use SVG\Nodes\Structures\SVGUse;
8
use SVG\Nodes\SVGNode;
9
use SVG\Nodes\SVGNodeContainer;
10
use SVG\Reading\SVGReader;
11
use SVG\SVG;
12
use Symfony\Component\Console\Attribute\AsCommand;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\Console\Style\SymfonyStyle;
19
20
#[AsCommand(
21
    name: 'theme:prepare-icons',
22
    description: 'Prepare the theme icons',
23
)]
24
#[Package('storefront')]
25
class ThemePrepareIconsCommand extends Command
26
{
27
    private SymfonyStyle $io;
28
29
    protected function configure(): void
30
    {
31
        $this->addArgument('path', InputArgument::REQUIRED, 'Path');
32
        $this->addArgument('package', InputArgument::REQUIRED, 'Package name');
33
        $this->addOption('fillcolor', 'f', InputOption::VALUE_REQUIRED, 'color for fill attribute in use tag');
34
        $this->addOption('fillrule', 'r', InputOption::VALUE_REQUIRED, 'fill-rule attribute for use tag');
35
        $this->addOption('cleanup', 'c', InputOption::VALUE_REQUIRED, 'cleanup all unnecessary attributes cleanup=true');
36
    }
37
38
    protected function execute(InputInterface $input, OutputInterface $output): int
39
    {
40
        $this->io = new SymfonyStyle($input, $output);
41
        $path = rtrim((string) $input->getArgument('path'), '/') . '/';
42
        $package = $input->getArgument('package');
43
44
        $fillcolor = $input->getOption('fillcolor');
45
        $fillrule = $input->getOption('fillrule');
46
        $verbose = $input->getOption('verbose');
47
48
        if (
49
            !empty($input->getOption('cleanup'))
50
            && $input->getOption('cleanup') !== 'true'
51
            && $input->getOption('cleanup') !== 'false'
52
        ) {
53
            $this->io->writeln(
54
                'Option cleanup can either be "true" or "false" but option is "'
55
                . $input->getOption('cleanup') . '" and will be handled as "false"'
56
            );
57
        }
58
59
        $cleanup = $input->getOption('cleanup') === 'true';
60
61
        if ($cleanup) {
62
            $this->io->writeln(
63
                'Cleanup is set. Processed Icons will be automatically cleaned. Please check the outcome.'
64
            );
65
        }
66
67
        $this->io = new SymfonyStyle($input, $output);
68
69
        $this->io->writeln('Start Icon preparation');
70
        $svgReader = new SVGReader();
71
        @mkdir($path . 'processed/');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

71
        /** @scrutinizer ignore-unhandled */ @mkdir($path . 'processed/');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
72
        $this->io->writeln('Created sub directory "processed" in working directory ' . str_replace(__DIR__, '', $path) . '.');
73
        $this->io->writeln('The processed icons will be written in the "processed" sub directory.');
74
75
        $files = glob($path . '*.svg');
76
        $processedCount = 0;
77
        if (!\is_array($files)) {
0 ignored issues
show
introduced by
The condition is_array($files) is always true.
Loading history...
78
            $this->io->warning('No svg files found in ' . $path);
79
80
            return self::SUCCESS;
81
        }
82
        foreach ($files as $file) {
83
            $svg = file_get_contents($file);
84
85
            if (!\is_string($svg)) {
86
                $this->io->warning('Could not read ' . $file . '.You have to handle this file by hand.');
87
88
                continue;
89
            }
90
91
            try {
92
                $svg = $svgReader->parseString($svg);
93
                if (!($svg instanceof SVG)) {
94
                    $this->io->warning('Could not read ' . $file . '.You have to handle this file by hand.');
95
96
                    continue;
97
                }
98
            } catch (\Exception $e) {
99
                $this->io->warning($e->getMessage() . ' ' . $file . \PHP_EOL . 'You have to handle this file by hand.');
100
101
                continue;
102
            }
103
104
            $defs = $svg->getDocument()->getChild(0);
105
            if (!($defs instanceof SVGDefs)) {
106
                $defs = new SVGDefs();
107
                foreach ($this->getChildren($svg->getDocument()) as $child) {
108
                    $svg->getDocument()->removeChild($child);
109
                    $defs->addChild($child);
110
                }
111
                $svg->getDocument()->addChild($defs);
112
            }
113
114
            $child = $defs->getChild(0);
115
116
            if ($child->getAttribute('id') === null || $cleanup) {
117
                $id = 'icons-' . $package . '-' . self::toKebabCase(basename($file, '.svg'));
118
                $child->setAttribute('id', $id);
119
            } else {
120
                $id = $child->getAttribute('id');
121
            }
122
123
            $use = null;
124
            foreach ($this->getChildren($svg->getDocument()) as $child) {
125
                if ($child instanceof SVGUse) {
126
                    $use = $child;
127
                }
128
            }
129
130
            if ($use === null) {
131
                $use = new SVGUse();
132
            }
133
134
            $use->setAttribute('xlink:href', '#' . $id);
135
            if ($fillcolor) {
136
                $use->setAttribute('fill', $fillcolor);
137
            } elseif ($cleanup) {
138
                $use->removeAttribute('fill');
139
            }
140
            if ($fillrule) {
141
                $use->setAttribute('fill-rule', $fillrule);
142
            } elseif ($cleanup) {
143
                $use->removeAttribute('fill-rule');
144
            }
145
146
            $svg->getDocument()->addChild($use);
147
148
            if ($cleanup) {
149
                $this->removeStyles($svg->getDocument());
150
            }
151
152
            file_put_contents($path . 'processed/' . basename($file), $svg->toXMLString(false));
153
154
            if ($verbose) {
155
                $this->io->writeln('Icon ' . $file . ' processed');
156
            }
157
            ++$processedCount;
158
        }
159
160
        $this->io->success('Processed ' . $processedCount . ' icons');
161
162
        return self::SUCCESS;
163
    }
164
165
    protected function removeStyles(SVGNode $child): void
166
    {
167
        foreach (array_keys($child->getSerializableStyles()) as $key) {
168
            $child->removeStyle($key);
169
        }
170
171
        if ($child instanceof SVGNodeContainer && $child->countChildren() > 0) {
172
            foreach ($this->getChildren($child) as $grandChild) {
173
                $this->removeStyles($grandChild);
174
            }
175
        }
176
    }
177
178
    private function getChildren(SVGNodeContainer $fragment): array
179
    {
180
        $children = [];
181
        for ($x = 0; $x < $fragment->countChildren(); ++$x) {
182
            $children[] = $fragment->getChild($x);
183
        }
184
185
        return $children;
186
    }
187
188
    private static function toKebabCase(string $str): string
189
    {
190
        return (string) preg_replace('/[^a-z0-9\-]/', '-', strtolower($str));
191
    }
192
}
193