Passed
Push — master ( 4109cd...3773bc )
by Dev
13:23
created

StaticAppGenerator::saveAsStatic()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 4
nop 2
dl 0
loc 21
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
namespace PiedWeb\CMSBundle\StaticGenerator;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use PiedWeb\CMSBundle\Entity\PageInterface;
7
use PiedWeb\CMSBundle\Entity\PageInterface as Page;
8
use PiedWeb\CMSBundle\Repository\PageRepository;
9
use PiedWeb\CMSBundle\Service\AppConfigHelper;
10
use PiedWeb\CMSBundle\Service\AppConfigHelper as App;
11
use PiedWeb\CMSBundle\Service\PageCanonicalService as PageCanonical;
12
use PiedWeb\CMSBundle\Utils\KernelTrait;
13
use PiedWeb\CMSBundle\Utils\GenerateLivePathForTrait;
14
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
15
use Symfony\Component\Filesystem\Filesystem;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpFoundation\RequestStack;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\HttpKernel\KernelInterface;
20
use Symfony\Component\Routing\Router;
21
use Symfony\Component\Routing\RouterInterface;
22
use Symfony\Contracts\Translation\TranslatorInterface;
23
use Twig\Environment as Twig;
24
use WyriHaximus\HtmlCompress\Factory as HtmlCompressor;
25
use WyriHaximus\HtmlCompress\HtmlCompressorInterface;
26
27
/**
28
 * Generate 1 App.
29
 */
30
class StaticAppGenerator
31
{
32
    use KernelTrait, GenerateLivePathForTrait;
33
34
    /**
35
     * Contain files relative to SEO wich will be hard copied.
36
     *
37
     * @var array
38
     */
39
    protected $robotsFiles = ['robots.txt'];
40
41
    /**
42
     * @var array
43
     */
44
    protected $dontCopy = ['index.php', '.htaccess'];
45
46
    /**
47
     * @var EntityManagerInterface
48
     */
49
    protected $em;
50
51
    /**
52
     * @var Filesystem
53
     */
54
    protected $filesystem;
55
56
    /**
57
     * @var Twig
58
     */
59
    protected $twig;
60
61
    /**
62
     * @var string
63
     */
64
    protected $webDir;
65
66
    /**
67
     * @var array
68
     */
69
    protected $apps;
70
    protected $app;
71
    protected $staticDomain;
72
    protected $mustGetPagesWithoutHost = true;
73
74
    /** var @string */
75
    protected $staticDir;
76
77
    /**
78
     * @var RequestStack
79
     */
80
    protected $requesStack;
81
82
    /**
83
     * @var \PiedWeb\CMSBundle\Service\PageCanonicalService
84
     */
85
    protected $pageCanonical;
86
87
    /**
88
     * @var TranslatorInterface
89
     */
90
    protected $translator;
91
92
    /**
93
     * @var HtmlCompressorInterface
94
     */
95
    protected $parser;
96
97
    /**
98
     * @var ParameterBagInterface
99
     */
100
    protected $params;
101
102
    /**
103
     * @var RouterInterface
104
     */
105
    protected $router;
106
107
    /**
108
     * Used in .htaccess generation.
109
     *
110
     * @var string
111
     */
112
    protected $redirections = '';
113
114
    public function __construct(
115
        EntityManagerInterface $em,
116
        Twig $twig,
117
        ParameterBagInterface $params,
118
        RequestStack $requesStack,
119
        PageCanonical $pageCanonical,
120
        TranslatorInterface $translator,
121
        RouterInterface $router,
122
        string $webDir,
123
        KernelInterface $kernel
124
    ) {
125
        $this->em = $em;
126
        $this->filesystem = new Filesystem();
127
        $this->twig = $twig;
128
        $this->params = $params;
129
        $this->requesStack = $requesStack;
130
        $this->webDir = $webDir;
131
        $this->pageCanonical = $pageCanonical;
132
        $this->translator = $translator;
133
        $this->router = $router;
134
        $this->apps = $this->params->get('pwc.apps');
135
        $this->parser = HtmlCompressor::construct();
136
137
        if (!method_exists($this->filesystem, 'dumpFile')) {
138
            throw new \RuntimeException('Method dumpFile() is not available. Upgrade your Filesystem.');
139
        }
140
141
        static::loadKernel($kernel);
142
        $this->kernel = $kernel;
143
    }
144
145
    public function generateAll($filter = null)
146
    {
147
148
        foreach ($this->apps as $app) {
149
            if ($filter && !in_array($filter, $app->getHost())) {
150
                    continue;
151
            }
152
            $this->generate($app, $this->mustGetPagesWithoutHost);
153
            //$this->generateStaticApp($app);
154
155
            $this->mustGetPagesWithoutHost = false;
156
        }
157
    }
158
159
    public function generateFromHost($host)
160
    {
161
        return $this->generateAll($host);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->generateAll($host) targeting PiedWeb\CMSBundle\Static...enerator::generateAll() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
162
    }
163
164
    /**
165
     * Main Logic is here.
166
     *
167
     * @throws \RuntimeException
168
     * @throws \LogicException
169
     */
170
    public function generate($app, $mustGetPagesWithoutHost = false)
171
    {
172
        $this->app = new App($app['hosts'][0], [$app]);
173
        $this->mustGetPagesWithoutHost = $mustGetPagesWithoutHost;
174
175
        $this->filesystem->remove($this->app->getStaticDir());
176
        $this->generatePages();
177
        $this->generateSitemaps();
178
        $this->generateErrorPages();
179
        $this->copyRobotsFiles();
180
        $this->generateServerManagerFile();
181
        $this->copyAssets();
182
        $this->copyMediaToDownload();
183
    }
184
185
    /**
186
     * Symlink doesn't work on github page, symlink only for apache if conf say OK to symlink.
187
     */
188
    protected function mustSymlink()
189
    {
190
        return $this->app->get('static_generateForApache') ? $this->app->get('static_symlinkMedia') : false;
191
    }
192
193
    /**
194
     * Generate .htaccess for Apache or CNAME for github
195
     * Must be run after generatePages() !!
196
     */
197
    protected function generateServerManagerFile()
198
    {
199
        if ($this->app->get('static_generateForApache')) {
200
            $this->generateHtaccess();
201
        } else { //if ($this->app['static_generateForGithubPages'])) {
202
            $this->generateCname();
203
        }
204
    }
205
206
    /**
207
     * Copy files relative to SEO (robots, sitemaps, etc.).
208
     */
209
    protected function copyRobotsFiles(): void
210
    {
211
        array_map([$this, 'copy'], $this->robotsFiles);
212
    }
213
214
    // todo
215
    // docs
216
    // https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
217
    protected function generateCname()
218
    {
219
        $this->filesystem->dumpFile($this->app->getStaticDir().'/CNAME', $this->app->getMainHost());
220
    }
221
222
    protected function generateHtaccess()
223
    {
224
        $htaccess = $this->twig->render('@PiedWebCMS/static/htaccess.twig', [
225
            'domain' => $this->app->getMainHost(),
226
            'redirections' => $this->redirections,
227
        ]);
228
        $this->filesystem->dumpFile($this->app->getStaticDir().'/.htaccess', $htaccess);
229
    }
230
231
    protected function copy(string $file): void
232
    {
233
        if (file_exists($file)) {
234
            copy(
235
                str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$file),
236
                $this->app->getStaticDir().'/'.$file
237
            );
238
        }
239
    }
240
241
    /**
242
     * Copy (or symlink) for all assets in public
243
     * (and media previously generated by liip in public).
244
     */
245
    protected function copyAssets(): void
246
    {
247
        $symlink = $this->mustSymlink();
248
249
        $dir = dir($this->webDir);
250
        while (false !== $entry = $dir->read()) {
251
            if ('.' == $entry || '..' == $entry) {
252
                continue;
253
            }
254
            if (!\in_array($entry, $this->robotsFiles) && !\in_array($entry, $this->dontCopy)) {
255
                //$this->symlink(
256
                if (true === $symlink) {
257
                    $this->filesystem->symlink(
258
                        str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$entry),
259
                        $this->app->getStaticDir().'/'.$entry
260
                    );
261
                } else {
262
                    $action = is_file($this->webDir.'/'.$entry) ? 'copy' : 'mirror';
263
                    $this->filesystem->$action($this->webDir.'/'.$entry, $this->app->getStaticDir().'/'.$entry);
264
                }
265
            }
266
        }
267
        $dir->close();
268
    }
269
270
    /**
271
     * Copy or Symlink "not image" media to download folder.
272
     *
273
     * @return void
274
     */
275
    protected function copyMediaToDownload()
276
    {
277
        $symlink = $this->mustSymlink();
278
279
        if (!file_exists($this->app->getStaticDir().'/download')) {
280
            $this->filesystem->mkdir($this->app->getStaticDir().'/download/');
281
            $this->filesystem->mkdir($this->app->getStaticDir().'/download/media');
282
        }
283
284
        $dir = dir($this->webDir.'/../media');
285
        while (false !== $entry = $dir->read()) {
286
            if ('.' == $entry || '..' == $entry) {
287
                continue;
288
            }
289
            // if the file is an image, it's ever exist (maybe it's slow to check every files)
290
            if (!file_exists($this->webDir.'/media/default/'.$entry)) {
291
                if (true === $symlink) {
292
                    $this->filesystem->symlink('../../../media/'.$entry, $this->app->getStaticDir().'/download/media/'.$entry);
293
                } else {
294
                    $this->filesystem->copy(
295
                        $this->webDir.'/../media/'.$entry,
296
                        $this->app->getStaticDir().'/download/media/'.$entry
297
                    );
298
                }
299
            }
300
        }
301
302
        //$this->filesystem->$action($this->webDir.'/../media', $this->app->getStaticDir().'/download/media');
303
    }
304
305
    protected function generateSitemaps(): void
306
    {
307
        foreach (explode('|', $this->params->get('pwc.locales')) as $locale) {
308
309
            foreach (['txt', 'xml'] as $format) {
310
                $this->generateSitemap($locale, $format);
311
            }
312
313
            $this->generateFeed($locale);
314
        }
315
    }
316
317
    protected function generateSitemap($locale, $format)
318
    {
319
        $liveUri = $this->generateLivePathFor($this->app->getMainHost(), 'piedweb_cms_page_sitemap', ['locale'=>$locale, '_format'=>$format]);
320
        $staticFile = $this->app->getStaticDir().'/sitemap'.$locale.'.'.$format; // todo get it from URI removing host
321
        $this->saveAsStatic($liveUri, $staticFile);
322
323
        if ($this->params->get('locale') == $locale ? '' : '.'.$locale) {
324
            $staticFile = $this->app->getStaticDir().'/sitemap.'.$format;
325
            $this->saveAsStatic($liveUri, $staticFile);
326
        }
327
    }
328
329
    protected function generateFeed($locale)
330
    {
331
        $liveUri = $this->generateLivePathFor($this->app->getMainHost(), 'piedweb_cms_page_main_feed', ['locale'=>$locale]);
332
        $staticFile = $this->app->getStaticDir().'/feed'.$locale.'.xml';
333
        $this->saveAsStatic($liveUri, $staticFile);
334
335
        if ($this->params->get('locale') == $locale ? '' : '.'.$locale) {
336
            $staticFile = $this->app->getStaticDir().'/feed.xml';
337
            $this->saveAsStatic($liveUri, $staticFile);
338
        }
339
    }
340
341
    /**
342
     * The function cache redirection found during generatePages and
343
     * format in self::$redirection the content for the .htaccess.
344
     *
345
     * @return void
346
     */
347
    protected function addRedirection(Page $page)
348
    {
349
        $this->redirections .= 'Redirect ';
350
        $this->redirections .= $page->getRedirectionCode().' ';
351
        $this->redirections .= $this->pageCanonical->generatePathForPage($page->getRealSlug());
352
        $this->redirections .= ' '.$page->getRedirection();
353
        $this->redirections .= PHP_EOL;
354
    }
355
356
    protected function generatePages(): void
357
    {
358
        $qb = $this->getPageRepository()->getQueryToFindPublished('p');
359
        $qb = $this->getPageRepository()->andHost($qb, $this->app->getMainHost(), $this->mustGetPagesWithoutHost);
360
        $pages = $qb->getQuery()->getResult();
361
362
        foreach ($pages as $page) {
363
            $this->generatePage($page);
364
            //if ($page->getRealSlug()) $this->generateFeedFor($page);
365
        }
366
367
    }
368
369
    protected function generatePage(Page $page)
370
    {
371
        /**/
372
        // check if it's a redirection
373
        if (false !== $page->getRedirection()) {
374
            $this->addRedirection($page);
375
376
            return;
377
        }
378
        /**/
379
380
        $this->saveAsStatic($this->generateLivePathFor($page), $this->generateFilePath($page));
381
    }
382
383
    protected function saveAsStatic($liveUri, $destination)
384
    {
385
        $request = Request::create($liveUri);
386
387
        $response = static::$appKernel->handle($request);
388
389
        if ($response->isRedirect()) {
390
            // todo
391
            //$this->addRedirection($liveUri, getRedirectUri)
392
            return;
393
        }
394
        elseif (200 != $response->getStatusCode()) {
395
            //$this->kernel = static::$appKernel;
396
            if ($response->getStatusCode() === 500 && $this->kernel->getEnvironment() == 'dev')
397
                exit($this->kernel->handle($request));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
398
399
            return;
400
        }
401
402
        $content = $this->compress($response->getContent());
403
        $this->filesystem->dumpFile($destination, $content);
404
    }
405
406
    protected function compress($html)
407
    {
408
        return $this->parser->compress($html);
409
    }
410
411
    protected function generateFilePath(Page $page)
412
    {
413
        $slug = '' == $page->getRealSlug() ? 'index' : $page->getRealSlug();
414
        $route = $this->pageCanonical->generatePathForPage($slug);
415
416
        return $this->app->getStaticDir().$route.'.html';
417
    }
418
419
    /**
420
     * Generate static file for feed indexing children pages
421
     * (only if children pages exists).
422
     *
423
     * @return void
424
     */
425
    protected function generateFeedFor(Page $page)
426
    {
427
        $liveUri = $this->generateLivePathFor($page, 'piedweb_cms_page_feed');
428
        $staticFile = preg_replace('/.html$/', '.xml', $this->generateFilePath($page));
429
        $this->saveAsStatic($liveUri, $staticFile);
430
    }
431
432
    protected function generateErrorPages(): void
433
    {
434
        $this->generateErrorPage();
435
436
        // todo i18n error in .htaccess
437
        $locales = explode('|', $this->params->get('pwc.locales'));
438
439
        foreach ($locales as $locale) {
440
            $this->filesystem->mkdir($this->app->getStaticDir().'/'.$locale);
441
            $this->generateErrorPage($locale);
442
        }
443
    }
444
445
    protected function generateErrorPage($locale = null, $uri = '404.html')
446
    {
447
        if (null !== $locale) {
448
            $request = new Request();
449
            $request->setLocale($locale);
450
            $this->requesStack->push($request);
451
        }
452
453
        $dump = $this->parser->compress($this->twig->render('@Twig/Exception/error.html.twig'));
454
        $this->filesystem->dumpFile($this->app->getStaticDir().(null !== $locale ? '/'.$locale : '').'/'.$uri, $dump);
455
    }
456
457
    protected function getPageRepository(): PageRepository
458
    {
459
        return $this->em->getRepository($this->params->get('pwc.entity_page'));
460
    }
461
}
462