Passed
Push — master ( abdb51...0ee754 )
by Dev
16:27 queued 01:19
created

StaticService::copyRobotsFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 0
cts 1
cp 0
crap 2
1
<?php
2
3
namespace PiedWeb\CMSBundle\Service;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use PiedWeb\CMSBundle\Entity\PageInterface as Page;
7
use PiedWeb\CMSBundle\Repository\PageRepository;
8
use PiedWeb\CMSBundle\Service\ConfigHelper as Helper;
9
use PiedWeb\CMSBundle\Service\PageCanonicalService as PageCanonical;
10
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
11
use Symfony\Component\Filesystem\Filesystem;
12
use Symfony\Component\HttpFoundation\Request;
13
use Symfony\Component\HttpFoundation\RequestStack;
14
use Symfony\Contracts\Translation\TranslatorInterface;
15
use Twig\Environment as Twig;
16
use WyriHaximus\HtmlCompress\Factory as HtmlCompressor;
17
use WyriHaximus\HtmlCompress\Parser as HtmlCompressorParser;
0 ignored issues
show
Bug introduced by
The type WyriHaximus\HtmlCompress\Parser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
19
class StaticService
20
{
21
    /**
22
     * Contain files relative to SEO wich will be hard copied.
23
     *
24
     * @var array
25
     */
26
    protected $robotsFiles = ['robots.txt', 'feed.xml', 'sitemap.xml', 'sitemap.txt'];
27
28
    /**
29
     * @var array
30
     */
31
    protected $dontCopy = ['index.php', '.htaccess'];
32
33
    /**
34
     * @var EntityManagerInterface
35
     */
36
    protected $em;
37
38
    /**
39
     * @var Filesystem
40
     */
41
    protected $filesystem;
42
43
    /**
44
     * @var Twig
45
     */
46
    protected $twig;
47
48
    /**
49
     * @var string
50
     */
51
    protected $webDir;
52
53
    /**
54
     * @var array
55
     */
56
    protected $apps;
57
    protected $app;
58
    protected $staticDomain;
59
    protected $isFirst = true;
60
61
    /** var @string */
62
    protected $staticDir;
63
64
    /**
65
     * @var RequestStack
66
     */
67
    protected $requesStack;
68
69
    /**
70
     * @var \PiedWeb\CMSBundle\Service\PageCanonicalService
71
     */
72
    protected $pageCanonical;
73
74
    /**
75
     * @var TranslatorInterface
76
     */
77
    protected $translator;
78
79
    /**
80
     * @var HtmlCompressorParser
81
     */
82
    protected $parser;
83
84
    /**
85
     * @var ParameterBagInterface
86
     */
87
    protected $params;
88
89
    /**
90
     * Used in .htaccess generation.
91
     *
92
     * @var string
93
     */
94
    protected $redirections = '';
95
96
    public function __construct(
97
        EntityManagerInterface $em,
98
        Twig $twig,
99
        ParameterBagInterface $params,
100
        RequestStack $requesStack,
101
        PageCanonical $pageCanonical,
102
        TranslatorInterface $translator,
103
        string $webDir
104
    ) {
105
        $this->em = $em;
106
        $this->filesystem = new Filesystem();
107
        $this->twig = $twig;
108
        $this->params = $params;
109
        $this->requesStack = $requesStack;
110
        $this->webDir = $webDir;
111
        $this->pageCanonical = $pageCanonical;
112
        $this->translator = $translator;
113
        $this->apps = $this->params->get('pwc.apps');
114
        $this->parser = HtmlCompressor::construct();
0 ignored issues
show
Documentation Bug introduced by
It seems like WyriHaximus\HtmlCompress\Factory::construct() of type WyriHaximus\HtmlCompress\HtmlCompressor is incompatible with the declared type WyriHaximus\HtmlCompress\Parser of property $parser.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
115
    }
116
117
    public function dump()
118
    {
119
        if (!method_exists($this->filesystem, 'dumpFile')) {
120
            throw new \RuntimeException('Method dumpFile() is not available. Upgrade your Filesystem.');
121
        }
122
123
        foreach ($this->apps as $app) {
124
            $this->generateStaticApp($app);
125
126
            $this->isFirst = false;
127
        }
128
    }
129
130
    /**
131
     * Main Logic is here.
132
     *
133
     * @throws \RuntimeException
134
     * @throws \LogicException
135
     */
136
    protected function generateStaticApp($app)
137
    {
138
        $this->app = $app;
139
        $this->staticDir = $app['static_dir'];
140
        $this->staticDomain = $app['hosts'][0];
141
142
        $this->filesystem->remove($this->staticDir);
143
144
        $this->generatePages();
145
        $this->generateSitemaps();
146
        $this->generateErrorPages();
147
        $this->copyRobotsFiles();
148
        $this->generateServerManagerFile();
149
        $this->copyAssets();
150
        $this->copyMediaToDownload();
151
    }
152
153
    /**
154
     * Symlink doesn't work on github page, symlink only for apache if conf say OK to symlink.
155
     */
156
    protected function mustSymlink()
157
    {
158
        return $this->app['static_generateForApache'] ? $this->app['static_symlinkMedia'] : false;
159
    }
160
161
    /**
162
     * Generate .htaccess for Apache or CNAME for github
163
     * Must be run after generatePages() !!
164
     */
165
    protected function generateServerManagerFile()
166
    {
167
        if ($this->app['static_generateForApache']) {
168
            $this->generateHtaccess();
169
        } else { //if ($this->app['static_generateForGithubPages'])) {
170
            $this->generateCname();
171
        }
172
    }
173
174
    /**
175
     * Copy files relative to SEO (robots, sitemaps, etc.).
176
     */
177
    protected function copyRobotsFiles(): void
178
    {
179
        array_map([$this, 'copy'], $this->robotsFiles);
180
    }
181
182
    // todo
183
    // docs
184
    // https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
185
    protected function generateCname()
186
    {
187
        $this->filesystem->dumpFile($this->staticDir.'/CNAME', $this->staticDomain);
188
    }
189
190
    protected function generateHtaccess()
191
    {
192
        $htaccess = $this->twig->render('@PiedWebCMS/static/htaccess.twig', [
193
            'domain' => $this->staticDomain,
194
            'redirections' => $this->redirections,
195
        ]);
196
        $this->filesystem->dumpFile($this->staticDir.'/.htaccess', $htaccess);
197
    }
198
199
    protected function copy(string $file): void
200
    {
201
        if (file_exists($file)) {
202
            copy(
203
                str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$file),
204
                $this->staticDir.'/'.$file
205
            );
206
        }
207
    }
208
209
    /**
210
     * Copy (or symlink) for all assets in public
211
     * (and media previously generated by liip in public).
212
     */
213
    protected function copyAssets(): void
214
    {
215
        $symlink = $this->mustSymlink();
216
217
        $dir = dir($this->webDir);
218
        while (false !== $entry = $dir->read()) {
219
            if ('.' == $entry || '..' == $entry) {
220
                continue;
221
            }
222
            if (!in_array($entry, $this->robotsFiles) && !in_array($entry, $this->dontCopy)) {
223
                //$this->symlink(
224
                if (true === $symlink) {
225
                    $this->filesystem->symlink(
226
                        str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$entry),
227
                        $this->staticDir.'/'.$entry
228
                    );
229
                } else {
230
                    $action = is_file($this->webDir.'/'.$entry) ? 'copy' : 'mirror';
231
                    $this->filesystem->$action($this->webDir.'/'.$entry, $this->staticDir.'/'.$entry);
232
                }
233
            }
234
        }
235
        $dir->close();
236
    }
237
238
    /**
239
     * Copy or Symlink "not image" media to download folder.
240
     *
241
     * @return void
242
     */
243
    protected function copyMediaToDownload()
244
    {
245
        $symlink = $this->mustSymlink();
246
247
        if (!file_exists($this->staticDir.'/download')) {
248
            $this->filesystem->mkdir($this->staticDir.'/download/');
249
            $this->filesystem->mkdir($this->staticDir.'/download/media');
250
        }
251
252
        $dir = dir($this->webDir.'/../media');
253
        while (false !== $entry = $dir->read()) {
254
            if ('.' == $entry || '..' == $entry) {
255
                continue;
256
            }
257
            // if the file is an image, it's ever exist (maybe it's slow to check every files)
258
            if (!file_exists($this->webDir.'/media/default/'.$entry)) {
259
                if (true === $symlink) {
260
                    $this->filesystem->symlink('../../../media/'.$entry, $this->staticDir.'/download/media/'.$entry);
261
                } else {
262
                    $this->filesystem->copy(
263
                        $this->webDir.'/../media/'.$entry,
264
                        $this->staticDir.'/download/media/'.$entry
265
                    );
266
                }
267
            }
268
        }
269
270
        //$this->filesystem->$action($this->webDir.'/../media', $this->staticDir.'/download/media');
271
    }
272
273
    protected function generatePages(): void
274
    {
275
        $pages = $this->getPages();
276
277
        foreach ($pages as $page) {
278
            $this->generatePage($page);
279
            $this->generateFeedFor($page);
280
        }
281
    }
282
283
    protected function generateSitemaps(): void
284
    {
285
        foreach (explode('|', $this->params->get('pwc.locales')) as $locale) {
286
            $request = new Request();
287
            $request->setLocale($locale);
288
289
            $this->requesStack->push($request);
290
291
            foreach (['txt', 'xml'] as $format) {
292
                $this->generateSitemap($locale, $format);
293
            }
294
295
            $this->generateFeed($locale);
296
        }
297
    }
298
299
    protected function generateSitemap($locale, $format)
300
    {
301
        $pages = $this->getPageRepository()->getIndexablePages(
302
            $this->app['hosts'][0],
303
            $this->isFirst,
304
            $locale,
305
            $this->params->get('locale')
306
        )->getQuery()->getResult();
307
308
        if (!$pages) {
309
            return;
310
        }
311
312
        $dump = $this->twig->render('@PiedWebCMS/page/sitemap.'.$format.'.twig', [
313
            'pages' => $pages,
314
            'app_base_url' => $this->app['base_url'],
315
        ]);
316
317
        $this->filesystem->dumpFile(
318
            $this->staticDir.'/sitemap'.($this->params->get('locale') == $locale ? '' : '.'.$locale).'.'.$format,
319
            $dump
320
        );
321
    }
322
323
    protected function generateFeed($locale)
324
    {
325
        $pages = $this->getPageRepository()->getIndexablePages(
326
            $this->app['hosts'][0],
327
            $this->isFirst,
328
            $locale,
329
            $this->params->get('locale'),
330
            3
331
        )->getQuery()->getResult();
332
333
        // Retrieve info from homepage, for i18n, assuming it's named with locale
334
        $helper = Helper::get($this->requesStack->getCurrentRequest(), $this->params);
0 ignored issues
show
Bug introduced by
It seems like $this->requesStack->getCurrentRequest() can also be of type null; however, parameter $request of PiedWeb\CMSBundle\Service\ConfigHelper::get() does only seem to accept Symfony\Component\HttpFoundation\Request, maybe add an additional type check? ( Ignorable by Annotation )

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

334
        $helper = Helper::get(/** @scrutinizer ignore-type */ $this->requesStack->getCurrentRequest(), $this->params);
Loading history...
335
        $LocaleHomepage = $this->getPageRepository()
336
            ->getPage($locale, $this->app['hosts'][0], $this->app['hosts'][0] === $helper->getFirstHost());
337
        $page = $LocaleHomepage ?? $this->getPageRepository()
338
            ->getPage('homepage', $this->app['hosts'][0], $this->app['hosts'][0] === $helper->getFirstHost());
339
340
        $dump = $this->twig->render('@PiedWebCMS/page/rss.xml.twig', [
341
            'pages' => $pages,
342
            'page' => $page,
343
            'feedUri' => 'feed'.($this->params->get('locale') == $locale ? '' : '.'.$locale).'.xml',
344
            'app_name' => $this->app['name'],
345
            'app_base_url' => $this->app['base_url'],
346
        ]);
347
348
        $this->filesystem->dumpFile(
349
            $this->staticDir.'/feed'.($this->params->get('locale') == $locale ? '' : '.'.$locale).'.xml',
350
            $dump
351
        );
352
    }
353
354
    /**
355
     * The function cache redirection found during generatePages and
356
     * format in self::$redirection the content for the .htaccess.
357
     *
358
     * @return void
359
     */
360
    protected function addRedirection(Page $page)
361
    {
362
        $this->redirections .= 'Redirect ';
363
        $this->redirections .= $page->getRedirectionCode().' ';
364
        $this->redirections .= $this->pageCanonical->generatePathForPage($page->getRealSlug());
365
        $this->redirections .= ' '.$page->getRedirection();
366
        $this->redirections .= PHP_EOL;
367
    }
368
369
    public function generatePage(Page $page)
370
    {
371
        // set current locale to avoid twig error
372
        $request = new Request();
373
        $request->setLocale($page->getLocale());
374
        $this->requesStack->push($request);
375
376
        //$this->translator->setLocale($page->getLocale());
377
378
        // check if it's a redirection
379
        if (false !== $page->getRedirection()) {
380
            $this->addRedirection($page);
381
382
            return;
383
        }
384
385
        $dump = $this->render($page);
386
        $this->filesystem->dumpFile($this->getFilePath($page), $dump);
387
    }
388
389
    protected function getFilePath(Page $page)
390
    {
391
        $slug = '' == $page->getRealSlug() ? 'index' : $page->getRealSlug();
392
        $route = $this->pageCanonical->generatePathForPage($slug);
393
394
        return $this->staticDir.$route.'.html';
395
    }
396
397
    /**
398
     * Generate static file for feed indexing children pages
399
     * (only if children pages exists).
400
     *
401
     * @return void
402
     */
403
    protected function generateFeedFor(Page $page)
404
    {
405
        if ($page->getChildrenPages()->count() > 0) {
406
            $dump = $this->renderFeed($page);
407
            $this->filesystem->dumpFile(preg_replace('/.html$/', '.xml', $this->getFilePath($page)), $dump);
408
        }
409
    }
410
411
    protected function generateErrorPages(): void
412
    {
413
        $this->generateErrorPage();
414
415
        // todo i18n error in .htaccess
416
        $locales = explode('|', $this->params->get('pwc.locales'));
417
418
        foreach ($locales as $locale) {
419
            $this->filesystem->mkdir($this->staticDir.'/'.$locale);
420
            $this->generateErrorPage($locale);
421
        }
422
    }
423
424
    protected function generateErrorPage($locale = null, $uri = '404.html')
425
    {
426
        if (null !== $locale) {
427
            $request = new Request();
428
            $request->setLocale($locale);
429
            $this->requesStack->push($request);
430
        }
431
432
        $dump = $this->parser->compress($this->twig->render('@Twig/Exception/error.html.twig'));
433
        $this->filesystem->dumpFile($this->staticDir.(null !== $locale ? '/'.$locale : '').'/'.$uri, $dump);
434
    }
435
436
    protected function getPageRepository(): PageRepository
437
    {
438
        return $this->em->getRepository($this->params->get('pwc.entity_page'));
439
    }
440
441
    protected function getPages()
442
    {
443
        $query = $this->getPageRepository()->getQueryToFindPublished('p')
444
            ->andWhere('(p.host = :h'.($this->isFirst ? ' OR p.host IS NULL' : '').')')
445
            ->setParameter('h', $this->staticDomain)
446
        ;
447
448
        return $query->getQuery()->getResult();
449
    }
450
451
    protected function render(Page $page): string
452
    {
453
        $template = $this->app['default_page_template'] ?? $this->params->get('pwc.default_page_template');
454
455
        return $this->parser->compress($this->twig->render($template, [
456
            'page' => $page,
457
            'app_base_url' => $this->app['base_url'],
458
            'app_name' => $this->app['name'],
459
            'app_color' => $this->app['color'],
460
        ]));
461
    }
462
463
    // todo i18n feed ...
464
    protected function renderFeed(Page $page): string
465
    {
466
        $template = '@PiedWebCMS/page/rss.xml.twig';
467
468
        return $this->parser->compress($this->twig->render($template, [
469
            'page' => $page,
470
            'app_base_url' => $this->app['base_url'],
471
            'app_name' => $this->app['name'],
472
            'app_color' => $this->app['color'],
473
        ]));
474
    }
475
}
476