Passed
Push — master ( 4017d9...9757db )
by Dev
10:15
created

StaticAppGenerator::copy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PiedWeb\CMSBundle\Extension\StaticGenerator;
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\AppConfigHelper as App;
9
use PiedWeb\CMSBundle\Service\PageCanonicalService as PageCanonical;
10
use PiedWeb\CMSBundle\Utils\GenerateLivePathForTrait;
11
use PiedWeb\CMSBundle\Utils\KernelTrait;
12
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
13
use Symfony\Component\Filesystem\Filesystem;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Symfony\Component\HttpKernel\KernelInterface;
17
use Symfony\Component\Routing\RouterInterface;
18
use Symfony\Contracts\Translation\TranslatorInterface;
19
use Twig\Environment as Twig;
20
use WyriHaximus\HtmlCompress\Factory as HtmlCompressor;
21
use WyriHaximus\HtmlCompress\HtmlCompressorInterface;
22
23
/**
24
 * Generate 1 App.
25
 */
26
class StaticAppGenerator
27
{
28
    use GenerateLivePathForTrait;
29
    use KernelTrait;
30
31
    /**
32
     * Contain files relative to SEO wich will be hard copied.
33
     *
34
     * @var array
35
     */
36
    protected $robotsFiles = ['robots.txt'];
37
38
    /**
39
     * @var array
40
     */
41
    protected $dontCopy = ['index.php', '.htaccess'];
42
43
    /**
44
     * @var EntityManagerInterface
45
     */
46
    protected $em;
47
48
    /**
49
     * @var Filesystem
50
     */
51
    protected $filesystem;
52
53
    /**
54
     * @var Twig
55
     */
56
    protected $twig;
57
58
    /**
59
     * @var string
60
     */
61
    protected $webDir;
62
63
    /**
64
     * @var array
65
     */
66
    protected $apps;
67
    protected $app;
68
    protected $staticDomain;
69
    protected $mustGetPagesWithoutHost = true;
70
71
    /** var @string */
72
    protected $staticDir;
73
74
    /**
75
     * @var RequestStack
76
     */
77
    protected $requesStack;
78
79
    /**
80
     * @var \PiedWeb\CMSBundle\Service\PageCanonicalService
81
     */
82
    protected $pageCanonical;
83
84
    /**
85
     * @var TranslatorInterface
86
     */
87
    protected $translator;
88
89
    /**
90
     * @var HtmlCompressorInterface
91
     */
92
    protected $parser;
93
94
    /**
95
     * @var ParameterBagInterface
96
     */
97
    protected $params;
98
99
    /**
100
     * @var RouterInterface
101
     */
102
    protected $router;
103
104
    /**
105
     * Used in .htaccess generation.
106
     *
107
     * @var string
108
     */
109
    protected $redirections = '';
110
111
    public function __construct(
112
        EntityManagerInterface $em,
113
        Twig $twig,
114
        ParameterBagInterface $params,
115
        RequestStack $requesStack,
116
        PageCanonical $pageCanonical,
117
        TranslatorInterface $translator,
118
        RouterInterface $router,
119
        string $webDir,
120
        KernelInterface $kernel
121
    ) {
122
        $this->em = $em;
123
        $this->filesystem = new Filesystem();
124
        $this->twig = $twig;
125
        $this->params = $params;
126
        $this->requesStack = $requesStack;
127
        $this->webDir = $webDir;
128
        $this->pageCanonical = $pageCanonical;
129
        $this->translator = $translator;
130
        $this->router = $router;
131
        $this->apps = $this->params->get('pwc.apps');
132
        $this->parser = HtmlCompressor::construct();
133
134
        if (!method_exists($this->filesystem, 'dumpFile')) {
135
            throw new \RuntimeException('Method dumpFile() is not available. Upgrade your Filesystem.');
136
        }
137
138
        static::loadKernel($kernel);
139
        $this->kernel = $kernel;
140
    }
141
142
    public function generateAll($filter = null)
143
    {
144
        foreach ($this->apps as $app) {
145
            if ($filter && !\in_array($filter, $app->getHost())) {
146
                continue;
147
            }
148
            $this->generate($app, $this->mustGetPagesWithoutHost);
149
            //$this->generateStaticApp($app);
150
151
            $this->mustGetPagesWithoutHost = false;
152
        }
153
    }
154
155
    public function generateFromHost($host)
156
    {
157
        return $this->generateAll($host);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->generateAll($host) targeting PiedWeb\CMSBundle\Extens...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...
158
    }
159
160
    /**
161
     * Main Logic is here.
162
     *
163
     * @throws \RuntimeException
164
     * @throws \LogicException
165
     */
166
    public function generate($app, $mustGetPagesWithoutHost = false)
167
    {
168
        $this->app = new App($app['hosts'][0], [$app]);
169
        $this->mustGetPagesWithoutHost = $mustGetPagesWithoutHost;
170
171
        $this->filesystem->remove($this->app->getStaticDir());
172
        $this->generatePages();
173
        $this->generateSitemaps();
174
        $this->generateErrorPages();
175
        $this->copyRobotsFiles();
176
        $this->generateServerManagerFile();
177
        $this->copyAssets();
178
        $this->copyMediaToDownload();
179
    }
180
181
    /**
182
     * Symlink doesn't work on github page, symlink only for apache if conf say OK to symlink.
183
     */
184
    protected function mustSymlink()
185
    {
186
        return $this->app->get('static_generateForApache') ? $this->app->get('static_symlinkMedia') : false;
187
    }
188
189
    /**
190
     * Generate .htaccess for Apache or CNAME for github
191
     * Must be run after generatePages() !!
192
     */
193
    protected function generateServerManagerFile()
194
    {
195
        if ($this->app->get('static_generateForApache')) {
196
            $this->generateHtaccess();
197
        } else { //if ($this->app['static_generateForGithubPages'])) {
198
            $this->generateCname();
199
        }
200
    }
201
202
    /**
203
     * Copy files relative to SEO (robots, sitemaps, etc.).
204
     */
205
    protected function copyRobotsFiles(): void
206
    {
207
        array_map([$this, 'copy'], $this->robotsFiles);
208
    }
209
210
    // todo
211
    // docs
212
    // https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
213
    protected function generateCname()
214
    {
215
        $this->filesystem->dumpFile($this->app->getStaticDir().'/CNAME', $this->app->getMainHost());
216
    }
217
218
    protected function generateHtaccess()
219
    {
220
        $htaccess = $this->twig->render('@PiedWebCMS/static/htaccess.twig', [
221
            'domain' => $this->app->getMainHost(),
222
            'redirections' => $this->redirections,
223
        ]);
224
        $this->filesystem->dumpFile($this->app->getStaticDir().'/.htaccess', $htaccess);
225
    }
226
227
    protected function copy(string $file): void
228
    {
229
        if (file_exists($file)) {
230
            copy(
231
                str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$file),
232
                $this->app->getStaticDir().'/'.$file
233
            );
234
        }
235
    }
236
237
    /**
238
     * Copy (or symlink) for all assets in public
239
     * (and media previously generated by liip in public).
240
     */
241
    protected function copyAssets(): void
242
    {
243
        $symlink = $this->mustSymlink();
244
245
        $dir = dir($this->webDir);
246
        while (false !== $entry = $dir->read()) {
247
            if ('.' == $entry || '..' == $entry) {
248
                continue;
249
            }
250
            if (!\in_array($entry, $this->robotsFiles) && !\in_array($entry, $this->dontCopy)) {
251
                //$this->symlink(
252
                if (true === $symlink) {
253
                    $this->filesystem->symlink(
254
                        str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$entry),
255
                        $this->app->getStaticDir().'/'.$entry
256
                    );
257
                } else {
258
                    $action = is_file($this->webDir.'/'.$entry) ? 'copy' : 'mirror';
259
                    $this->filesystem->$action($this->webDir.'/'.$entry, $this->app->getStaticDir().'/'.$entry);
260
                }
261
            }
262
        }
263
        $dir->close();
264
    }
265
266
    /**
267
     * Copy or Symlink "not image" media to download folder.
268
     *
269
     * @return void
270
     */
271
    protected function copyMediaToDownload()
272
    {
273
        $symlink = $this->mustSymlink();
274
275
        if (!file_exists($this->app->getStaticDir().'/download')) {
276
            $this->filesystem->mkdir($this->app->getStaticDir().'/download/');
277
            $this->filesystem->mkdir($this->app->getStaticDir().'/download/media');
278
        }
279
280
        $dir = dir($this->webDir.'/../media');
281
        while (false !== $entry = $dir->read()) {
282
            if ('.' == $entry || '..' == $entry) {
283
                continue;
284
            }
285
            // if the file is an image, it's ever exist (maybe it's slow to check every files)
286
            if (!file_exists($this->webDir.'/media/default/'.$entry)) {
287
                if (true === $symlink) {
288
                    $this->filesystem->symlink(
289
                        '../../../media/'.$entry,
290
                        $this->app->getStaticDir().'/download/media/'.$entry
291
                    );
292
                } else {
293
                    $this->filesystem->copy(
294
                        $this->webDir.'/../media/'.$entry,
295
                        $this->app->getStaticDir().'/download/media/'.$entry
296
                    );
297
                }
298
            }
299
        }
300
301
        //$this->filesystem->$action($this->webDir.'/../media', $this->app->getStaticDir().'/download/media');
302
    }
303
304
    protected function generateSitemaps(): void
305
    {
306
        foreach (explode('|', $this->params->get('pwc.locales')) as $locale) {
307
            foreach (['txt', 'xml'] as $format) {
308
                $this->generateSitemap($locale, $format);
309
            }
310
311
            $this->generateFeed($locale);
312
        }
313
    }
314
315
    protected function generateSitemap($locale, $format)
316
    {
317
        $liveUri = $this->generateLivePathFor(
318
            $this->app->getMainHost(),
319
            'piedweb_cms_page_sitemap',
320
            ['locale' => $locale, '_format' => $format]
321
        );
322
        $staticFile = $this->app->getStaticDir().'/sitemap'.$locale.'.'.$format; // todo get it from URI removing host
323
        $this->saveAsStatic($liveUri, $staticFile);
324
325
        if ($this->params->get('locale') == $locale ? '' : '.'.$locale) {
326
            $staticFile = $this->app->getStaticDir().'/sitemap.'.$format;
327
            $this->saveAsStatic($liveUri, $staticFile);
328
        }
329
    }
330
331
    protected function generateFeed($locale)
332
    {
333
        $liveUri = $this->generateLivePathFor(
334
            $this->app->getMainHost(),
335
            'piedweb_cms_page_main_feed',
336
            ['locale' => $locale]
337
        );
338
        $staticFile = $this->app->getStaticDir().'/feed'.$locale.'.xml';
339
        $this->saveAsStatic($liveUri, $staticFile);
340
341
        if ($this->params->get('locale') == $locale ? '' : '.'.$locale) {
342
            $staticFile = $this->app->getStaticDir().'/feed.xml';
343
            $this->saveAsStatic($liveUri, $staticFile);
344
        }
345
    }
346
347
    /**
348
     * The function cache redirection found during generatePages and
349
     * format in self::$redirection the content for the .htaccess.
350
     *
351
     * @return void
352
     */
353
    protected function addRedirection(Page $page)
354
    {
355
        $this->redirections .= 'Redirect ';
356
        $this->redirections .= $page->getRedirectionCode().' ';
357
        $this->redirections .= $this->pageCanonical->generatePathForPage($page->getRealSlug());
358
        $this->redirections .= ' '.$page->getRedirection();
359
        $this->redirections .= PHP_EOL;
360
    }
361
362
    protected function generatePages(): void
363
    {
364
        $qb = $this->getPageRepository()->getQueryToFindPublished('p');
365
        $qb = $this->getPageRepository()->andHost($qb, $this->app->getMainHost(), $this->mustGetPagesWithoutHost);
366
        $pages = $qb->getQuery()->getResult();
367
368
        foreach ($pages as $page) {
369
            $this->generatePage($page);
370
            //if ($page->getRealSlug()) $this->generateFeedFor($page);
371
        }
372
    }
373
374
    protected function generatePage(Page $page)
375
    {
376
        // check if it's a redirection
377
        if (false !== $page->getRedirection()) {
378
            $this->addRedirection($page);
379
380
            return;
381
        }
382
383
        $this->saveAsStatic($this->generateLivePathFor($page), $this->generateFilePath($page));
384
    }
385
386
    protected function saveAsStatic($liveUri, $destination)
387
    {
388
        $request = Request::create($liveUri);
389
390
        $response = static::$appKernel->handle($request);
391
392
        if ($response->isRedirect()) {
393
            // todo
394
            //$this->addRedirection($liveUri, getRedirectUri)
395
            return;
396
        } elseif (200 != $response->getStatusCode()) {
397
            //$this->kernel = static::$appKernel;
398
            if (500 === $response->getStatusCode() && 'dev' == $this->kernel->getEnvironment()) {
399
                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...
400
            }
401
402
            return;
403
        }
404
405
        $content = $this->compress($response->getContent());
406
        $this->filesystem->dumpFile($destination, $content);
407
    }
408
409
    protected function compress($html)
410
    {
411
        return $this->parser->compress($html);
412
    }
413
414
    protected function generateFilePath(Page $page)
415
    {
416
        $slug = '' == $page->getRealSlug() ? 'index' : $page->getRealSlug();
417
        $route = $this->pageCanonical->generatePathForPage($slug);
418
419
        return $this->app->getStaticDir().$route.'.html';
420
    }
421
422
    /**
423
     * Generate static file for feed indexing children pages
424
     * (only if children pages exists).
425
     *
426
     * @return void
427
     */
428
    protected function generateFeedFor(Page $page)
429
    {
430
        $liveUri = $this->generateLivePathFor($page, 'piedweb_cms_page_feed');
431
        $staticFile = preg_replace('/.html$/', '.xml', $this->generateFilePath($page));
432
        $this->saveAsStatic($liveUri, $staticFile);
433
    }
434
435
    protected function generateErrorPages(): void
436
    {
437
        $this->generateErrorPage();
438
439
        // todo i18n error in .htaccess
440
        $locales = explode('|', $this->params->get('pwc.locales'));
441
442
        foreach ($locales as $locale) {
443
            $this->filesystem->mkdir($this->app->getStaticDir().'/'.$locale);
444
            $this->generateErrorPage($locale);
445
        }
446
    }
447
448
    protected function generateErrorPage($locale = null, $uri = '404.html')
449
    {
450
        if (null !== $locale) {
451
            $request = new Request();
452
            $request->setLocale($locale);
453
            $this->requesStack->push($request);
454
        }
455
456
        $dump = $this->parser->compress($this->twig->render('@Twig/Exception/error.html.twig'));
457
        $this->filesystem->dumpFile($this->app->getStaticDir().(null !== $locale ? '/'.$locale : '').'/'.$uri, $dump);
458
    }
459
460
    protected function getPageRepository(): PageRepository
461
    {
462
        return $this->em->getRepository($this->params->get('pwc.entity_page'));
463
    }
464
}
465