Passed
Push — master ( 0ee754...043c6c )
by Dev
11:08
created

StaticService::render()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 4
b 0
f 0
nc 1
nop 1
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 2
rs 10
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
18
class StaticService
19
{
20
    /**
21
     * Contain files relative to SEO wich will be hard copied.
22
     *
23
     * @var array
24
     */
25
    protected $robotsFiles = ['robots.txt', 'feed.xml', 'sitemap.xml', 'sitemap.txt'];
26
27
    /**
28
     * @var array
29
     */
30
    protected $dontCopy = ['index.php', '.htaccess'];
31
32
    /**
33
     * @var EntityManagerInterface
34
     */
35
    protected $em;
36
37
    /**
38
     * @var Filesystem
39
     */
40
    protected $filesystem;
41
42
    /**
43
     * @var Twig
44
     */
45
    protected $twig;
46
47
    /**
48
     * @var string
49
     */
50
    protected $webDir;
51
52
    /**
53
     * @var array
54
     */
55
    protected $apps;
56
    protected $app;
57
    protected $staticDomain;
58
    protected $isFirst = true;
59
60
    /** var @string */
61
    protected $staticDir;
62
63
    /**
64
     * @var RequestStack
65
     */
66
    protected $requesStack;
67
68
    /**
69
     * @var \PiedWeb\CMSBundle\Service\PageCanonicalService
70
     */
71
    protected $pageCanonical;
72
73
    /**
74
     * @var TranslatorInterface
75
     */
76
    protected $translator;
77
78
    /**
79
     * @var HtmlCompressor
80
     */
81
    protected $parser;
82
83
    /**
84
     * @var ParameterBagInterface
85
     */
86
    protected $params;
87
88
    /**
89
     * Used in .htaccess generation.
90
     *
91
     * @var string
92
     */
93
    protected $redirections = '';
94
95
    public function __construct(
96
        EntityManagerInterface $em,
97
        Twig $twig,
98
        ParameterBagInterface $params,
99
        RequestStack $requesStack,
100
        PageCanonical $pageCanonical,
101
        TranslatorInterface $translator,
102
        string $webDir
103
    ) {
104
        $this->em = $em;
105
        $this->filesystem = new Filesystem();
106
        $this->twig = $twig;
107
        $this->params = $params;
108
        $this->requesStack = $requesStack;
109
        $this->webDir = $webDir;
110
        $this->pageCanonical = $pageCanonical;
111
        $this->translator = $translator;
112
        $this->apps = $this->params->get('pwc.apps');
113
        $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\Factory 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...
114
    }
115
116
    public function dump()
117
    {
118
        if (!method_exists($this->filesystem, 'dumpFile')) {
119
            throw new \RuntimeException('Method dumpFile() is not available. Upgrade your Filesystem.');
120
        }
121
122
        foreach ($this->apps as $app) {
123
            $this->generateStaticApp($app);
124
125
            $this->isFirst = false;
126
        }
127
    }
128
129
    /**
130
     * Main Logic is here.
131
     *
132
     * @throws \RuntimeException
133
     * @throws \LogicException
134
     */
135
    protected function generateStaticApp($app)
136
    {
137
        $this->app = $app;
138
        $this->staticDir = $app['static_dir'];
139
        $this->staticDomain = $app['hosts'][0];
140
141
        $this->filesystem->remove($this->staticDir);
142
143
        $this->generatePages();
144
        $this->generateSitemaps();
145
        $this->generateErrorPages();
146
        $this->copyRobotsFiles();
147
        $this->generateServerManagerFile();
148
        $this->copyAssets();
149
        $this->copyMediaToDownload();
150
    }
151
152
    /**
153
     * Symlink doesn't work on github page, symlink only for apache if conf say OK to symlink.
154
     */
155
    protected function mustSymlink()
156
    {
157
        return $this->app['static_generateForApache'] ? $this->app['static_symlinkMedia'] : false;
158
    }
159
160
    /**
161
     * Generate .htaccess for Apache or CNAME for github
162
     * Must be run after generatePages() !!
163
     */
164
    protected function generateServerManagerFile()
165
    {
166
        if ($this->app['static_generateForApache']) {
167
            $this->generateHtaccess();
168
        } else { //if ($this->app['static_generateForGithubPages'])) {
169
            $this->generateCname();
170
        }
171
    }
172
173
    /**
174
     * Copy files relative to SEO (robots, sitemaps, etc.).
175
     */
176
    protected function copyRobotsFiles(): void
177
    {
178
        array_map([$this, 'copy'], $this->robotsFiles);
179
    }
180
181
    // todo
182
    // docs
183
    // https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
184
    protected function generateCname()
185
    {
186
        $this->filesystem->dumpFile($this->staticDir.'/CNAME', $this->staticDomain);
187
    }
188
189
    protected function generateHtaccess()
190
    {
191
        $htaccess = $this->twig->render('@PiedWebCMS/static/htaccess.twig', [
192
            'domain' => $this->staticDomain,
193
            'redirections' => $this->redirections,
194
        ]);
195
        $this->filesystem->dumpFile($this->staticDir.'/.htaccess', $htaccess);
196
    }
197
198
    protected function copy(string $file): void
199
    {
200
        if (file_exists($file)) {
201
            copy(
202
                str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$file),
203
                $this->staticDir.'/'.$file
204
            );
205
        }
206
    }
207
208
    /**
209
     * Copy (or symlink) for all assets in public
210
     * (and media previously generated by liip in public).
211
     */
212
    protected function copyAssets(): void
213
    {
214
        $symlink = $this->mustSymlink();
215
216
        $dir = dir($this->webDir);
217
        while (false !== $entry = $dir->read()) {
218
            if ('.' == $entry || '..' == $entry) {
219
                continue;
220
            }
221
            if (!in_array($entry, $this->robotsFiles) && !in_array($entry, $this->dontCopy)) {
222
                //$this->symlink(
223
                if (true === $symlink) {
224
                    $this->filesystem->symlink(
225
                        str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$entry),
226
                        $this->staticDir.'/'.$entry
227
                    );
228
                } else {
229
                    $action = is_file($this->webDir.'/'.$entry) ? 'copy' : 'mirror';
230
                    $this->filesystem->$action($this->webDir.'/'.$entry, $this->staticDir.'/'.$entry);
231
                }
232
            }
233
        }
234
        $dir->close();
235
    }
236
237
    /**
238
     * Copy or Symlink "not image" media to download folder.
239
     *
240
     * @return void
241
     */
242
    protected function copyMediaToDownload()
243
    {
244
        $symlink = $this->mustSymlink();
245
246
        if (!file_exists($this->staticDir.'/download')) {
247
            $this->filesystem->mkdir($this->staticDir.'/download/');
248
            $this->filesystem->mkdir($this->staticDir.'/download/media');
249
        }
250
251
        $dir = dir($this->webDir.'/../media');
252
        while (false !== $entry = $dir->read()) {
253
            if ('.' == $entry || '..' == $entry) {
254
                continue;
255
            }
256
            // if the file is an image, it's ever exist (maybe it's slow to check every files)
257
            if (!file_exists($this->webDir.'/media/default/'.$entry)) {
258
                if (true === $symlink) {
259
                    $this->filesystem->symlink('../../../media/'.$entry, $this->staticDir.'/download/media/'.$entry);
260
                } else {
261
                    $this->filesystem->copy(
262
                        $this->webDir.'/../media/'.$entry,
263
                        $this->staticDir.'/download/media/'.$entry
264
                    );
265
                }
266
            }
267
        }
268
269
        //$this->filesystem->$action($this->webDir.'/../media', $this->staticDir.'/download/media');
270
    }
271
272
    protected function generatePages(): void
273
    {
274
        $qb = $this->getPageRepository()->getQueryToFindPublished('p');
275
        $qb = $this->getPageRepository()->andHost($qb, $this->staticDomain, $this->isFirst);
276
        $pages = $qb->getQuery()->getResult();
277
278
        foreach ($pages as $page) {
279
            $this->generatePage($page);
280
            $this->generateFeedFor($page);
281
        }
282
    }
283
284
    protected function generateSitemaps(): void
285
    {
286
        foreach (explode('|', $this->params->get('pwc.locales')) as $locale) {
287
            $request = new Request();
288
            $request->setLocale($locale);
289
290
            $this->requesStack->push($request);
291
292
            foreach (['txt', 'xml'] as $format) {
293
                $this->generateSitemap($locale, $format);
294
            }
295
296
            $this->generateFeed($locale);
297
        }
298
    }
299
300
    protected function generateSitemap($locale, $format)
301
    {
302
        $pages = $this->getPageRepository()->getIndexablePages(
303
            $this->app['hosts'][0],
304
            $this->isFirst,
305
            $locale,
306
            $this->params->get('locale')
307
        )->getQuery()->getResult();
308
309
        if (!$pages) {
310
            return;
311
        }
312
313
        $dump = $this->twig->render('@PiedWebCMS/page/sitemap.'.$format.'.twig', [
314
            'pages' => $pages,
315
            'app_base_url' => $this->app['base_url'],
316
        ]);
317
318
        $this->filesystem->dumpFile(
319
            $this->staticDir.'/sitemap'.($this->params->get('locale') == $locale ? '' : '.'.$locale).'.'.$format,
320
            $dump
321
        );
322
    }
323
324
    protected function generateFeed($locale)
325
    {
326
        $pages = $this->getPageRepository()->getIndexablePages(
327
            $this->app['hosts'][0],
328
            $this->isFirst,
329
            $locale,
330
            $this->params->get('locale'),
331
            3
332
        )->getQuery()->getResult();
333
334
        // Retrieve info from homepage, for i18n, assuming it's named with locale
335
        $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

335
        $helper = Helper::get(/** @scrutinizer ignore-type */ $this->requesStack->getCurrentRequest(), $this->params);
Loading history...
336
        $LocaleHomepage = $this->getPageRepository()
337
            ->getPage($locale, $this->app['hosts'][0], $this->app['hosts'][0] === $helper->getFirstHost());
338
        $page = $LocaleHomepage ?? $this->getPageRepository()
339
            ->getPage('homepage', $this->app['hosts'][0], $this->app['hosts'][0] === $helper->getFirstHost());
340
341
        $dump = $this->twig->render('@PiedWebCMS/page/rss.xml.twig', [
342
            'pages' => $pages,
343
            'page' => $page,
344
            'feedUri' => 'feed'.($this->params->get('locale') == $locale ? '' : '.'.$locale).'.xml',
345
            'app_name' => $this->app['name'],
346
            'app_base_url' => $this->app['base_url'],
347
        ]);
348
349
        $this->filesystem->dumpFile(
350
            $this->staticDir.'/feed'.($this->params->get('locale') == $locale ? '' : '.'.$locale).'.xml',
351
            $dump
352
        );
353
    }
354
355
    /**
356
     * The function cache redirection found during generatePages and
357
     * format in self::$redirection the content for the .htaccess.
358
     *
359
     * @return void
360
     */
361
    protected function addRedirection(Page $page)
362
    {
363
        $this->redirections .= 'Redirect ';
364
        $this->redirections .= $page->getRedirectionCode().' ';
365
        $this->redirections .= $this->pageCanonical->generatePathForPage($page->getRealSlug());
366
        $this->redirections .= ' '.$page->getRedirection();
367
        $this->redirections .= PHP_EOL;
368
    }
369
370
    public function generatePage(Page $page)
371
    {
372
        // set current locale to avoid twig error
373
        $request = new Request();
374
        $request->setLocale($page->getLocale());
375
        $this->requesStack->push($request);
376
377
        // check if it's a redirection
378
        if (false !== $page->getRedirection()) {
379
            $this->addRedirection($page);
380
381
            return;
382
        }
383
384
        $dump = $this->render($page);
385
        $this->filesystem->dumpFile($this->getFilePath($page), $dump);
386
    }
387
388
    protected function getFilePath(Page $page)
389
    {
390
        $slug = '' == $page->getRealSlug() ? 'index' : $page->getRealSlug();
391
        $route = $this->pageCanonical->generatePathForPage($slug);
392
393
        return $this->staticDir.$route.'.html';
394
    }
395
396
    /**
397
     * Generate static file for feed indexing children pages
398
     * (only if children pages exists).
399
     *
400
     * @return void
401
     */
402
    protected function generateFeedFor(Page $page)
403
    {
404
        if ($page->getChildrenPages()->count() > 0) {
405
            $dump = $this->renderFeed($page);
406
            $this->filesystem->dumpFile(preg_replace('/.html$/', '.xml', $this->getFilePath($page)), $dump);
407
        }
408
    }
409
410
    protected function generateErrorPages(): void
411
    {
412
        $this->generateErrorPage();
413
414
        // todo i18n error in .htaccess
415
        $locales = explode('|', $this->params->get('pwc.locales'));
416
417
        foreach ($locales as $locale) {
418
            $this->filesystem->mkdir($this->staticDir.'/'.$locale);
419
            $this->generateErrorPage($locale);
420
        }
421
    }
422
423
    protected function generateErrorPage($locale = null, $uri = '404.html')
424
    {
425
        if (null !== $locale) {
426
            $request = new Request();
427
            $request->setLocale($locale);
428
            $this->requesStack->push($request);
429
        }
430
431
        $dump = $this->parser->compress($this->twig->render('@Twig/Exception/error.html.twig'));
0 ignored issues
show
Bug introduced by
The method compress() does not exist on WyriHaximus\HtmlCompress\Factory. ( Ignorable by Annotation )

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

431
        /** @scrutinizer ignore-call */ 
432
        $dump = $this->parser->compress($this->twig->render('@Twig/Exception/error.html.twig'));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
432
        $this->filesystem->dumpFile($this->staticDir.(null !== $locale ? '/'.$locale : '').'/'.$uri, $dump);
433
    }
434
435
    protected function getPageRepository(): PageRepository
436
    {
437
        return $this->em->getRepository($this->params->get('pwc.entity_page'));
438
    }
439
440
    protected function render(Page $page): string
441
    {
442
        $template = $this->app['default_page_template'] ?? $this->params->get('pwc.default_page_template');
443
444
        return $this->parser->compress($this->twig->render($template, [
445
            'page' => $page,
446
            'app_base_url' => $this->app['base_url'],
447
            'app_name' => $this->app['name'],
448
            'app_color' => $this->app['color'],
449
        ]));
450
    }
451
452
    protected function renderFeed(Page $page): string
453
    {
454
        $template = '@PiedWebCMS/page/rss.xml.twig';
455
456
        return $this->parser->compress($this->twig->render($template, [
457
            'page' => $page,
458
            'app_base_url' => $this->app['base_url'],
459
            'app_name' => $this->app['name'],
460
        ]));
461
    }
462
}
463