Completed
Pull Request — 2.x (#345)
by
unknown
01:31
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
     * @var ContainerInterface|null
56
     */
57
    private $container;
58
59
    public function __construct(?RouterInterface $router = null, ?SourceManager $sitemapManager = null, ?Filesystem $fs = null)
60
    {
61
        $this->router = $router;
62
        $this->sitemapManager = $sitemapManager;
63
        $this->fs = $fs;
64
65
        parent::__construct();
66
    }
67
68
    /**
69
     * @deprecated
70
     * @todo Remove deprecated methods, remove interface implementation, cleanup 'use' block.
71
     * @todo Make arguments of __construct required instead of optional.
72
     */
73
    public function setContainer(ContainerInterface $container = null)
74
    {
75
        @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...
76
77
        $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...
78
        $this->sitemapManager = $container->get('sonata.seo.sitemap.manager');
79
        $this->fs = $container->get('filesystem');
80
        $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...
81
    }
82
83
    /**
84
     * @deprecated
85
     */
86
    public function getContainer(): ?ContainerInterface
87
    {
88
        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...
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function configure()
95
    {
96
        $this->setName('sonata:seo:sitemap');
97
98
        $this->addArgument('folder', InputArgument::REQUIRED, 'The folder to store the sitemap.xml file');
99
        $this->addArgument('host', InputArgument::REQUIRED, 'Set the host');
100
        $this->addOption('scheme', null, InputOption::VALUE_OPTIONAL, 'Set the scheme', 'http');
101
        $this->addOption('baseurl', null, InputOption::VALUE_OPTIONAL, 'Set the base url', '');
102
        $this->addOption('sitemap_path', null, InputOption::VALUE_OPTIONAL, 'Set the sitemap relative path (if in a specific folder)', '');
103
104
        $this->setDescription('Create a sitemap');
105
        $this->setHelp(<<<'EOT'
106
The <info>sonata:seo:sitemap</info> command create new sitemap files (index + sitemap).
107
108
EOT
109
        );
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function execute(InputInterface $input, OutputInterface $output)
116
    {
117
        $host = $input->getArgument('host');
118
        $scheme = $input->getOption('scheme');
119
        $baseUrl = $input->getOption('baseurl');
120
        $permanentFolder = $input->getArgument('folder');
121
        $appendPath = $input->hasOption('sitemap_path') ? $input->getOption('sitemap_path') : $baseUrl;
122
123
        $this->getContext()->setHost($host);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 117 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...
124
        $this->getContext()->setScheme($scheme);
125
        $this->getContext()->setBaseUrl($baseUrl);
126
127
        $tempFolder = $this->createTempFolder($output);
128
        if ($tempFolder === null) {
129
            $output->writeln('<error>The temporary folder already exists</error>');
130
            $output->writeln('<error>If the task is not running please delete this folder</error>');
131
            return 1;
132
        }
133
134
        $output->writeln(sprintf('Generating sitemap - this can take a while'));
135
        $this->generateSitemap($tempFolder, $scheme, $host, $appendPath);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 117 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...
136
137
        $output->writeln(sprintf('Moving temporary file to %s ...', $permanentFolder));
138
        $this->moveTemporaryFile($tempFolder, $permanentFolder);
0 ignored issues
show
Bug introduced by
It seems like $permanentFolder defined by $input->getArgument('folder') on line 120 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...
139
140
        $output->writeln('Cleanup ...');
141
        $this->fs->remove($tempFolder);
142
143
        $output->writeln('<info>done!</info>');
144
    }
145
146
    private function getContext(): RequestContext
147
    {
148
        return $this->router->getContext();
149
    }
150
151
    /**
152
     * Creates temporary folder if one does not exist.
153
     * @return string|null Folder name or null if folder is already exist.
154
     */
155
    private function createTempFolder(OutputInterface $output)
156
    {
157
        $tempFolder = sys_get_temp_dir().'/sonata_sitemap_'.md5(__DIR__);
158
159
        $output->writeln(sprintf('Creating temporary folder: %s', $tempFolder));
160
161
        if ($this->fs->exists($tempFolder)) {
162
            return null;
163
        }
164
165
        $this->fs->mkdir($tempFolder);
166
167
        return $tempFolder;
168
    }
169
170
    /**
171
     * @throws \Exception
172
     */
173
    private function generateSitemap(string $folder, string $scheme, string $host, string $appendPath): void
174
    {
175
        foreach ($this->sitemapManager as $group => $sitemap) {
176
            $write = new SitemapWriter($folder, $group, $sitemap->types, false);
177
178
            try {
179
                Handler::create($sitemap->sources, $write)->export();
180
            } catch (\Exception $e) {
181
                $this->fs->remove($folder);
182
183
                throw $e;
184
            }
185
        }
186
187
        // generate global sitemap index
188
        SitemapWriter::generateSitemapIndex(
189
            $folder,
190
            sprintf('%s://%s%s', $scheme, $host, $appendPath),
191
            'sitemap*.xml',
192
            'sitemap.xml'
193
        );
194
    }
195
196
    private function moveTemporaryFile(string $tempFolder, string $permanentFolder): void
197
    {
198
        $oldFiles = Finder::create()->files()->name('sitemap*.xml')->in($permanentFolder);
199
        foreach ($oldFiles as $file) {
200
            $this->fs->remove($file->getRealPath());
201
        }
202
203
        $newFiles = Finder::create()->files()->name('sitemap*.xml')->in($tempFolder);
204
        foreach ($newFiles as $file) {
205
            $this->fs->rename($file->getRealPath(), sprintf('%s/%s', $permanentFolder, $file->getFilename()));
206
        }
207
    }
208
}
209