Completed
Pull Request — 2.x (#345)
by
unknown
01:17
created

SitemapGeneratorCommand::createTempFolder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 1
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 $fs;
52
53
    /**
54
     * @deprecated
55
     *
56
     * @var ContainerInterface|null
57
     */
58
    private $container;
59
60
    public function __construct(?RouterInterface $router = null, ?SourceManager $sitemapManager = null, ?Filesystem $fs = null)
61
    {
62
        $this->router = $router;
63
        $this->sitemapManager = $sitemapManager;
64
        $this->fs = $fs;
65
66
        parent::__construct();
67
    }
68
69
    /**
70
     * @deprecated
71
     *
72
     * @todo Remove deprecated methods, remove interface implementation, cleanup 'use' block.
73
     * @todo Make arguments of __construct required instead of optional.
74
     */
75
    public function setContainer(ContainerInterface $container = null)
76
    {
77
        @trigger_error('Please, use the new method of 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...
78
79
        $this->router = $container->get('router');
0 ignored issues
show
Bug introduced by
It seems like $container is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
80
        $this->sitemapManager = $container->get('sonata.seo.sitemap.manager');
81
        $this->fs = $container->get('filesystem');
82
        $this->container = $container;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\SeoBundle\Command...atorCommand::$container has been deprecated.

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
85
    /**
86
     * @deprecated
87
     */
88
    public function getContainer(): ?ContainerInterface
89
    {
90
        @trigger_error('Please, avoid usage 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...
91
92
        return $this->container;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\SeoBundle\Command...atorCommand::$container has been deprecated.

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...
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function configure()
99
    {
100
        $this->setName('sonata:seo:sitemap');
101
102
        $this->addArgument('folder', InputArgument::REQUIRED, 'The folder to store the sitemap.xml file');
103
        $this->addArgument('host', InputArgument::REQUIRED, 'Set the host');
104
        $this->addOption('scheme', null, InputOption::VALUE_OPTIONAL, 'Set the scheme', 'http');
105
        $this->addOption('baseurl', null, InputOption::VALUE_OPTIONAL, 'Set the base url', '');
106
        $this->addOption('sitemap_path', null, InputOption::VALUE_OPTIONAL, 'Set the sitemap relative path (if in a specific folder)', '');
107
108
        $this->setDescription('Create a sitemap');
109
        $this->setHelp(<<<'EOT'
110
The <info>sonata:seo:sitemap</info> command create new sitemap files (index + sitemap).
111
112
EOT
113
        );
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function execute(InputInterface $input, OutputInterface $output)
120
    {
121
        $host = $input->getArgument('host');
122
        $scheme = $input->getOption('scheme');
123
        $baseUrl = $input->getOption('baseurl');
124
        $permanentFolder = $input->getArgument('folder');
125
        $appendPath = $input->hasOption('sitemap_path') ? $input->getOption('sitemap_path') : $baseUrl;
126
127
        $this->getContext()->setHost($host);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 121 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...
128
        $this->getContext()->setScheme($scheme);
129
        $this->getContext()->setBaseUrl($baseUrl);
130
131
        $tempFolder = $this->createTempFolder($output);
132
        if (null === $tempFolder) {
133
            $output->writeln('<error>The temporary folder already exists</error>');
134
            $output->writeln('<error>If the task is not running please delete this folder</error>');
135
136
            return 1;
137
        }
138
139
        $output->writeln(sprintf('Generating sitemap - this can take a while'));
140
        $this->generateSitemap($tempFolder, $scheme, $host, $appendPath);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 121 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...
141
142
        $output->writeln(sprintf('Moving temporary file to %s ...', $permanentFolder));
143
        $this->moveTemporaryFile($tempFolder, $permanentFolder);
0 ignored issues
show
Bug introduced by
It seems like $permanentFolder defined by $input->getArgument('folder') on line 124 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...
144
145
        $output->writeln('Cleanup ...');
146
        $this->fs->remove($tempFolder);
147
148
        $output->writeln('<info>done!</info>');
149
    }
150
151
    private function getContext(): RequestContext
152
    {
153
        return $this->router->getContext();
154
    }
155
156
    /**
157
     * Creates temporary folder if one does not exist.
158
     *
159
     * @return string|null Folder name or null if folder is already exist
160
     */
161
    private function createTempFolder(OutputInterface $output): ?string
162
    {
163
        $tempFolder = sys_get_temp_dir().'/sonata_sitemap_'.md5(__DIR__);
164
165
        $output->writeln(sprintf('Creating temporary folder: %s', $tempFolder));
166
167
        if ($this->fs->exists($tempFolder)) {
168
            return null;
169
        }
170
171
        $this->fs->mkdir($tempFolder);
172
173
        return $tempFolder;
174
    }
175
176
    /**
177
     * @throws \Exception
178
     */
179
    private function generateSitemap(string $folder, string $scheme, string $host, string $appendPath): void
180
    {
181
        foreach ($this->sitemapManager as $group => $sitemap) {
182
            $write = new SitemapWriter($folder, $group, $sitemap->types, false);
183
184
            try {
185
                Handler::create($sitemap->sources, $write)->export();
186
            } catch (\Exception $e) {
187
                $this->fs->remove($folder);
188
189
                throw $e;
190
            }
191
        }
192
193
        // generate global sitemap index
194
        SitemapWriter::generateSitemapIndex(
195
            $folder,
196
            sprintf('%s://%s%s', $scheme, $host, $appendPath),
197
            'sitemap*.xml',
198
            'sitemap.xml'
199
        );
200
    }
201
202
    private function moveTemporaryFile(string $tempFolder, string $permanentFolder): void
203
    {
204
        $oldFiles = Finder::create()->files()->name('sitemap*.xml')->in($permanentFolder);
205
        foreach ($oldFiles as $file) {
206
            $this->fs->remove($file->getRealPath());
207
        }
208
209
        $newFiles = Finder::create()->files()->name('sitemap*.xml')->in($tempFolder);
210
        foreach ($newFiles as $file) {
211
            $this->fs->rename($file->getRealPath(), sprintf('%s/%s', $permanentFolder, $file->getFilename()));
212
        }
213
    }
214
}
215