CodeGenerator::renderCodes()   C
last analyzed

Complexity

Conditions 9
Paths 256

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 22
nc 256
nop 2
dl 0
loc 40
rs 6.5222
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * CodeGenerator.php - Jaxon code generator
5
 *
6
 * Generate HTML, CSS and Javascript code for Jaxon.
7
 *
8
 * @package jaxon-core
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2016 Thierry Feuzeu <[email protected]>
11
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
12
 * @link https://github.com/jaxon-php/jaxon-core
13
 */
14
15
namespace Jaxon\Plugin\Code;
16
17
use Jaxon\Di\Container;
18
use Jaxon\Plugin\AbstractPlugin;
19
use Jaxon\Plugin\CodeGeneratorInterface;
20
use Jaxon\Utils\Http\UriException;
21
use Jaxon\Utils\Template\TemplateEngine;
22
23
use function array_map;
24
use function array_merge;
25
use function count;
26
use function implode;
27
use function is_subclass_of;
28
use function ksort;
29
use function md5;
30
use function trim;
31
32
class CodeGenerator
33
{
34
    /**
35
     * @var AssetManager
36
     */
37
    private $xAssetManager;
38
39
    /**
40
     * The classes that generate code
41
     *
42
     * @var array<string>
43
     */
44
    protected $aCodeGenerators = [];
45
46
    /**
47
     * @var string
48
     */
49
    protected $sJsOptions;
50
51
    /**
52
     * @var array
53
     */
54
    protected $aCss = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $aJs = [];
60
61
    /**
62
     * @var array
63
     */
64
    protected $aCodeJs = [];
65
66
    /**
67
     * @var array
68
     */
69
    protected $aCodeJsBefore = [];
70
71
    /**
72
     * @var array
73
     */
74
    protected $aCodeJsAfter = [];
75
76
    /**
77
     * @var array
78
     */
79
    protected $aCodeJsFiles = [];
80
81
    /**
82
     * @var bool
83
     */
84
    protected $bGenerated = false;
85
86
    /**
87
     * The constructor
88
     *
89
     * @param string $sVersion
90
     * @param Container $di
91
     * @param TemplateEngine $xTemplateEngine
92
     */
93
    public function __construct(private string $sVersion, private Container $di,
94
        private TemplateEngine $xTemplateEngine)
95
    {
96
        // The Jaxon library config is on top.
97
        $this->addCodeGenerator(ConfigScriptGenerator::class, 0);
98
        // The ready script comes after.
99
        $this->addCodeGenerator(ReadyScriptGenerator::class, 200);
100
    }
101
102
    /**
103
     * Add a code generator to the list
104
     *
105
     * @param string $sClassName    The code generator class
106
     * @param int $nPriority    The desired priority, used to order the plugins
107
     *
108
     * @return void
109
     */
110
    public function addCodeGenerator(string $sClassName, int $nPriority): void
111
    {
112
        while(isset($this->aCodeGenerators[$nPriority]))
113
        {
114
            $nPriority++;
115
        }
116
        $this->aCodeGenerators[$nPriority] = $sClassName;
117
    }
118
119
    /**
120
     * @param string $sClassName
121
     *
122
     * @return CodeGeneratorInterface
123
     */
124
    private function getCodeGenerator(string $sClassName): CodeGeneratorInterface
125
    {
126
        return $this->di->g($sClassName);
127
    }
128
129
    /**
130
     * Generate a hash for all the javascript code generated by the library
131
     *
132
     * @return string
133
     */
134
    public function getHash(): string
135
    {
136
        $aHashes = array_map(fn($sClassName) =>
137
            $this->getCodeGenerator($sClassName)->getHash(), $this->aCodeGenerators);
138
        $aHashes[] = $this->sVersion;
139
        return md5(implode('', $aHashes));
140
    }
141
142
    /**
143
     * Render a template in the 'plugins' subdir
144
     *
145
     * @param string $sTemplate    The template filename
146
     * @param array $aVars    The template variables
147
     *
148
     * @return string
149
     */
150
    private function render(string $sTemplate, array $aVars = []): string
151
    {
152
        $aVars['sJsOptions'] = $this->sJsOptions;
153
        return $this->xTemplateEngine->render("jaxon::plugins/$sTemplate", $aVars);
154
    }
155
156
    /**
157
     * Generate the Jaxon CSS and js codes for a given plugin
158
     *
159
     * @param CodeGeneratorInterface $xGenerator
160
     *
161
     * @return void
162
     */
163
    private function generatePluginCodes(CodeGeneratorInterface $xGenerator): void
164
    {
165
        if(!is_subclass_of($xGenerator, AbstractPlugin::class) ||
166
            $this->xAssetManager->shallIncludeAssets($xGenerator))
167
        {
168
            // HTML tags for CSS
169
            if(($sCss = trim($xGenerator->getCss(), " \n")) !== '')
170
            {
171
                $this->aCss[] = $sCss;
172
            }
173
            // HTML tags for js
174
            if(($sJs = trim($xGenerator->getJs(), " \n")) !== '')
175
            {
176
                $this->aJs[] = $sJs;
177
            }
178
        }
179
180
        // Additional js codes
181
        if(($xJsCode = $xGenerator->getJsCode()) !== null)
182
        {
183
            if(($sJs = trim($xJsCode->sJs, " \n")) !== '')
184
            {
185
                $this->aCodeJs[] = $sJs;
186
            }
187
            if(($sJsBefore = trim($xJsCode->sJsBefore, " \n")) !== '')
188
            {
189
                $this->aCodeJsBefore[] = $sJsBefore;
190
            }
191
            if(($sJsAfter = trim($xJsCode->sJsAfter, " \n")) !== '')
192
            {
193
                $this->aCodeJsAfter[] = $sJsAfter;
194
            }
195
            $this->aCodeJsFiles = array_merge($this->aCodeJsFiles, $xJsCode->aFiles);
196
        }
197
    }
198
199
    /**
200
     * Generate the Jaxon CSS ans js codes
201
     *
202
     * @return void
203
     * @throws UriException
204
     */
205
    private function generateCodes(): void
206
    {
207
        if($this->bGenerated)
208
        {
209
            return;
210
        }
211
212
        // We need the library to have been bootstrapped.
213
        $this->di->getBootstrap()->onBoot();
214
215
        // Sort the code generators by ascending priority
216
        ksort($this->aCodeGenerators);
217
218
        // Cannot be injected because of dependency loop.
219
        $this->xAssetManager = $this->di->getAssetManager();
220
221
        $this->sJsOptions = $this->xAssetManager->getJsOptions();
222
        foreach($this->aCodeGenerators as $sClassName)
223
        {
224
            $this->generatePluginCodes($this->getCodeGenerator($sClassName));
225
        }
226
227
        // Load the Jaxon lib js files, after the other libs js files.
228
        $this->aJs[] = trim($this->render('includes.js', [
229
            'aUrls' => $this->xAssetManager->getJsLibFiles(),
230
        ]));
231
232
        // The codes are already generated.
233
        $this->bGenerated = true;
234
    }
235
236
    /**
237
     * Get the HTML tags to include Jaxon CSS code and files into the page
238
     *
239
     * @return string
240
     * @throws UriException
241
     */
242
    public function getCss(): string
243
    {
244
        $this->generateCodes();
245
        return implode("\n\n", $this->aCss);
246
    }
247
248
    /**
249
     * Get the HTML tags to include Jaxon javascript files into the page
250
     *
251
     * @return string
252
     * @throws UriException
253
     */
254
    public function getJs(): string
255
    {
256
        $this->generateCodes();
257
        return implode("\n\n", $this->aJs);
258
    }
259
260
    /**
261
     * Get the Javascript code
262
     *
263
     * @return string
264
     */
265
    public function getJsScript(): string
266
    {
267
        $aJsScripts = [];
268
        foreach($this->aCodeGenerators as $sClassName)
269
        {
270
            $xGenerator = $this->getCodeGenerator($sClassName);
271
            // Javascript code
272
            if(($sJsScript = trim($xGenerator->getScript(), " \n")) !== '')
273
            {
274
                $aJsScripts[] = $sJsScript;
275
            }
276
        }
277
        return implode("\n\n", $aJsScripts);
278
    }
279
280
    /**
281
     * @param bool $bIncludeJs Also get the JS files
282
     * @param bool $bIncludeCss Also get the CSS files
283
     *
284
     * @return array<string>
285
     */
286
    private function renderCodes(bool $bIncludeJs, bool $bIncludeCss): array
287
    {
288
        $aCodes = [];
289
        if($bIncludeCss)
290
        {
291
            $aCodes[] = $this->getCss();
292
        }
293
        if($bIncludeJs)
294
        {
295
            $aCodes[] = $this->getJs();
296
        }
297
298
        $sUrl = !$this->xAssetManager->shallCreateJsFiles() ? '' :
299
            $this->xAssetManager->createJsFiles($this);
300
        // Wrap the js code into the corresponding HTML tag.
301
        $aCodes[] = $sUrl !== '' ?
302
            $this->render('include.js', ['sUrl' => $sUrl]) :
303
            $this->render('wrapper.js', ['sScript' => $this->getJsScript()]);
304
305
        // Wrap the js codes into HTML tags.
306
        if(count($this->aCodeJsBefore) > 0)
307
        {
308
            $sScript = implode("\n\n", $this->aCodeJsBefore);
309
            $aCodes[] = $this->render('wrapper.js', ['sScript' => $sScript]);
310
        }
311
        if(count($this->aCodeJs) > 0)
312
        {
313
            $sScript = implode("\n\n", $this->aCodeJs);
314
            $aCodes[] = $this->render('wrapper.js', ['sScript' => $sScript]);
315
        }
316
        if(count($this->aCodeJsFiles) > 0)
317
        {
318
            $aCodes[] = $this->render('includes.js', ['aUrls' => $this->aCodeJsFiles]);
319
        }
320
        if(count($this->aCodeJsAfter) > 0)
321
        {
322
            $sScript = implode("\n\n", $this->aCodeJsAfter);
323
            $aCodes[] = $this->render('wrapper.js', ['sScript' => $sScript]);
324
        }
325
        return $aCodes;
326
    }
327
328
    /**
329
     * Get the javascript code to be sent to the browser
330
     *
331
     * @param bool $bIncludeJs Also get the JS files
332
     * @param bool $bIncludeCss Also get the CSS files
333
     *
334
     * @return string
335
     * @throws UriException
336
     */
337
    public function getScript(bool $bIncludeJs, bool $bIncludeCss): string
338
    {
339
        $this->generateCodes();
340
        $aCodes = $this->renderCodes($bIncludeJs, $bIncludeCss);
341
        return implode("\n\n", $aCodes);
342
    }
343
}
344