Completed
Push — master ( 0632ca...63c21f )
by
unknown
05:31 queued 10s
created

SitemapGeneratorCommand::moveTemporaryFile()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 3
nc 4
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\SeoBundle\Command;
15
16
use Sonata\Exporter\Handler;
17
use Sonata\Exporter\Writer\SitemapWriter;
18
use Sonata\SeoBundle\Sitemap\SourceManager;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
use Symfony\Component\Filesystem\Filesystem;
27
use Symfony\Component\Finder\Finder;
28
use Symfony\Component\Routing\RequestContext;
29
use Symfony\Component\Routing\RouterInterface;
30
31
/**
32
 * Create a sitemap.
33
 *
34
 * @author Thomas Rabaix <[email protected]>
35
 */
36
class SitemapGeneratorCommand extends Command implements ContainerAwareInterface
37
{
38
    /**
39
     * @var RouterInterface
40
     */
41
    private $router;
42
43
    /**
44
     * @var SourceManager
45
     */
46
    private $sitemapManager;
47
48
    /**
49
     * @var Filesystem
50
     */
51
    private $filesystem;
52
53
    /**
54
     * @deprecated since sonata-project/seo-bundle 2.0
55
     *
56
     * @var ContainerInterface|null
57
     */
58
    private $container;
59
60
    public function __construct(
61
        ?RouterInterface $router = null,
62
        ?SourceManager $sitemapManager = null,
63
        ?Filesystem $filesystem = null
64
    ) {
65
        $this->router = $router;
66
        $this->sitemapManager = $sitemapManager;
67
        $this->filesystem = $filesystem;
68
69
        parent::__construct();
70
    }
71
72
    /**
73
     * @deprecated since sonata-project/seo-bundle 2.0
74
     *
75
     * NEXT_MAJOR Remove deprecated methods, remove interface implementation, cleanup 'use' block.
76
     * NEXT_MAJOR Make arguments of __construct required instead of optional.
77
     */
78
    public function setContainer(ContainerInterface $container = null): void
79
    {
80
        @trigger_error('Injection of container has been deprecated. Consider injection of each service you need in your console command declaration.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
81
82
        $this->container = $container;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\SeoBundle\Command...atorCommand::$container has been deprecated with message: since sonata-project/seo-bundle 2.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
83
84
        if (null === $container) {
85
            return;
86
        }
87
88
        $this->router = $container->get('router');
89
        $this->sitemapManager = $container->get('sonata.seo.sitemap.manager');
90
        $this->filesystem = $container->get('filesystem');
91
    }
92
93
    /**
94
     * @deprecated since sonata-project/seo-bundle 2.0
95
     */
96
    public function getContainer(): ?ContainerInterface
97
    {
98
        @trigger_error('Please, avoid injection of container in your services.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
99
100
        return $this->container;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\SeoBundle\Command...atorCommand::$container has been deprecated with message: since sonata-project/seo-bundle 2.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function configure(): void
107
    {
108
        $this->setName('sonata:seo:sitemap');
109
110
        $this->addArgument('dir', InputArgument::REQUIRED, 'The directory to store the sitemap.xml file');
111
        $this->addArgument('host', InputArgument::REQUIRED, 'Set the host');
112
        $this->addOption('scheme', null, InputOption::VALUE_OPTIONAL, 'Set the scheme', 'http');
113
        $this->addOption('baseurl', null, InputOption::VALUE_OPTIONAL, 'Set the base url', '');
114
        $this->addOption(
115
            'sitemap_path',
116
            null,
117
            InputOption::VALUE_OPTIONAL,
118
            'Set the sitemap relative path (if in a specific directory)',
119
            ''
120
        );
121
122
        $this->setDescription('Create a sitemap');
123
        $this->setHelp(<<<'EOT'
124
The <info>sonata:seo:sitemap</info> command create new sitemap files (index + sitemap).
125
126
EOT
127
        );
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function execute(InputInterface $input, OutputInterface $output)
134
    {
135
        $host = $input->getArgument('host');
136
        $scheme = $input->getOption('scheme');
137
        $baseUrl = $input->getOption('baseurl');
138
        $permanentDir = $input->getArgument('dir');
139
        $appendPath = $input->hasOption('sitemap_path') ? $input->getOption('sitemap_path') : $baseUrl;
140
141
        $this->getContext()->setHost($host);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 135 can also be of type array<integer,string> or null; however, Symfony\Component\Routin...questContext::setHost() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
142
        $this->getContext()->setScheme($scheme);
143
        $this->getContext()->setBaseUrl($baseUrl);
144
145
        $tempDir = $this->createTempDir($output);
146
        if (null === $tempDir) {
147
            $output->writeln('<error>The temporary directory already exists</error>');
148
            $output->writeln('<error>If the task is not running please delete this directory</error>');
149
150
            return 1;
151
        }
152
153
        $output->writeln(sprintf('Generating sitemap - this can take a while'));
154
        $this->generateSitemap($tempDir, $scheme, $host, $appendPath);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 135 can also be of type array<integer,string> or null; however, Sonata\SeoBundle\Command...mand::generateSitemap() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
155
156
        $output->writeln(sprintf('Moving temporary file to %s ...', $permanentDir));
157
        $this->moveTemporaryFile($tempDir, $permanentDir);
0 ignored issues
show
Bug introduced by
It seems like $permanentDir defined by $input->getArgument('dir') on line 138 can also be of type array<integer,string> or null; however, Sonata\SeoBundle\Command...nd::moveTemporaryFile() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
158
159
        $output->writeln('Cleanup ...');
160
        $this->filesystem->remove($tempDir);
161
162
        $output->writeln('<info>done!</info>');
163
    }
164
165
    private function getContext(): RequestContext
166
    {
167
        return $this->router->getContext();
168
    }
169
170
    /**
171
     * Creates temporary directory if one does not exist.
172
     *
173
     * @return string|null Directory name or null if directory is already exist
174
     */
175
    private function createTempDir(OutputInterface $output): ?string
176
    {
177
        $tempDir = sys_get_temp_dir().'/sonata_sitemap_'.md5(__DIR__);
178
179
        $output->writeln(sprintf('Creating temporary directory: %s', $tempDir));
180
181
        if ($this->filesystem->exists($tempDir)) {
182
            return null;
183
        }
184
185
        $this->filesystem->mkdir($tempDir);
186
187
        return $tempDir;
188
    }
189
190
    /**
191
     * @throws \Exception
192
     */
193
    private function generateSitemap(string $dir, string $scheme, string $host, string $appendPath): void
194
    {
195
        foreach ($this->sitemapManager as $group => $sitemap) {
196
            $write = new SitemapWriter($dir, $group, $sitemap->types, false);
197
198
            try {
199
                Handler::create($sitemap->sources, $write)->export();
200
            } catch (\Exception $e) {
201
                $this->filesystem->remove($dir);
202
203
                throw $e;
204
            }
205
        }
206
207
        // generate global sitemap index
208
        SitemapWriter::generateSitemapIndex(
209
            $dir,
210
            sprintf('%s://%s%s', $scheme, $host, $appendPath),
211
            'sitemap*.xml',
212
            'sitemap.xml'
213
        );
214
    }
215
216
    private function moveTemporaryFile(string $tempDir, string $permanentDir): void
217
    {
218
        $oldFiles = Finder::create()->files()->name('sitemap*.xml')->in($permanentDir);
219
        foreach ($oldFiles as $file) {
220
            $this->filesystem->remove($file->getRealPath());
221
        }
222
223
        $newFiles = Finder::create()->files()->name('sitemap*.xml')->in($tempDir);
224
        foreach ($newFiles as $file) {
225
            $this->filesystem->rename($file->getRealPath(), sprintf('%s/%s', $permanentDir, $file->getFilename()));
226
        }
227
    }
228
}
229