CodeGenerator::getCodeGenerator()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
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(function($sClassName) {
137
            return $this->getCodeGenerator($sClassName)->getHash();
138
        }, $this->aCodeGenerators);
139
        $aHashes[] = $this->sVersion;
140
        return md5(implode('', $aHashes));
141
    }
142
143
    /**
144
     * Render a template in the 'plugins' subdir
145
     *
146
     * @param string $sTemplate    The template filename
147
     * @param array $aVars    The template variables
148
     *
149
     * @return string
150
     */
151
    private function render(string $sTemplate, array $aVars = []): string
152
    {
153
        $aVars['sJsOptions'] = $this->sJsOptions;
154
        return $this->xTemplateEngine->render("jaxon::plugins/$sTemplate", $aVars);
155
    }
156
157
    /**
158
     * Generate the Jaxon CSS and js codes for a given plugin
159
     *
160
     * @param CodeGeneratorInterface $xGenerator
161
     *
162
     * @return void
163
     */
164
    private function generatePluginCodes(CodeGeneratorInterface $xGenerator): void
165
    {
166
        if(!is_subclass_of($xGenerator, AbstractPlugin::class) ||
167
            $this->xAssetManager->shallIncludeAssets($xGenerator))
168
        {
169
            // HTML tags for CSS
170
            if(($sCss = trim($xGenerator->getCss(), " \n")) !== '')
171
            {
172
                $this->aCss[] = $sCss;
173
            }
174
            // HTML tags for js
175
            if(($sJs = trim($xGenerator->getJs(), " \n")) !== '')
176
            {
177
                $this->aJs[] = $sJs;
178
            }
179
        }
180
181
        // Additional js codes
182
        if(($xJsCode = $xGenerator->getJsCode()) !== null)
183
        {
184
            if(($sJs = trim($xJsCode->sJs, " \n")) !== '')
185
            {
186
                $this->aCodeJs[] = $sJs;
187
            }
188
            if(($sJsBefore = trim($xJsCode->sJsBefore, " \n")) !== '')
189
            {
190
                $this->aCodeJsBefore[] = $sJsBefore;
191
            }
192
            if(($sJsAfter = trim($xJsCode->sJsAfter, " \n")) !== '')
193
            {
194
                $this->aCodeJsAfter[] = $sJsAfter;
195
            }
196
            $this->aCodeJsFiles = array_merge($this->aCodeJsFiles, $xJsCode->aFiles);
197
        }
198
    }
199
200
    /**
201
     * Generate the Jaxon CSS ans js codes
202
     *
203
     * @return void
204
     * @throws UriException
205
     */
206
    private function generateCodes(): void
207
    {
208
        if($this->bGenerated)
209
        {
210
            return;
211
        }
212
213
        $this->xAssetManager = $this->di->getAssetManager();
214
        $this->sJsOptions = $this->xAssetManager->getJsOptions();
215
216
        // Sort the code generators by ascending priority
217
        ksort($this->aCodeGenerators);
218
219
        foreach($this->aCodeGenerators as $sClassName)
220
        {
221
            $this->generatePluginCodes($this->getCodeGenerator($sClassName));
222
        }
223
224
        // Load the Jaxon lib js files, after the other libs js files.
225
        $this->aJs[] = trim($this->render('includes.js', [
226
            'aUrls' => $this->xAssetManager->getJsLibFiles(),
227
        ]));
228
229
        // The codes are already generated.
230
        $this->bGenerated = true;
231
    }
232
233
    /**
234
     * Get the HTML tags to include Jaxon CSS code and files into the page
235
     *
236
     * @return string
237
     * @throws UriException
238
     */
239
    public function getCss(): string
240
    {
241
        $this->generateCodes();
242
        return implode("\n\n", $this->aCss);
243
    }
244
245
    /**
246
     * Get the HTML tags to include Jaxon javascript files into the page
247
     *
248
     * @return string
249
     * @throws UriException
250
     */
251
    public function getJs(): string
252
    {
253
        $this->generateCodes();
254
        return implode("\n\n", $this->aJs);
255
    }
256
257
    /**
258
     * Get the Javascript code
259
     *
260
     * @return string
261
     */
262
    public function getJsScript(): string
263
    {
264
        $aJsScripts = [];
265
        foreach($this->aCodeGenerators as $sClassName)
266
        {
267
            $xGenerator = $this->getCodeGenerator($sClassName);
268
            // Javascript code
269
            if(($sJsScript = trim($xGenerator->getScript(), " \n")) !== '')
270
            {
271
                $aJsScripts[] = $sJsScript;
272
            }
273
        }
274
        return implode("\n\n", $aJsScripts);
275
    }
276
277
    /**
278
     * @param bool $bIncludeJs Also get the JS files
279
     * @param bool $bIncludeCss Also get the CSS files
280
     *
281
     * @return array<string>
282
     */
283
    private function renderCodes(bool $bIncludeJs, bool $bIncludeCss): array
284
    {
285
        $aCodes = [];
286
        if($bIncludeCss)
287
        {
288
            $aCodes[] = $this->getCss();
289
        }
290
        if($bIncludeJs)
291
        {
292
            $aCodes[] = $this->getJs();
293
        }
294
295
        $sUrl = !$this->xAssetManager->shallCreateJsFiles() ? '' :
296
            $this->xAssetManager->createJsFiles($this);
297
        // Wrap the js code into the corresponding HTML tag.
298
        $aCodes[] = $sUrl !== '' ?
299
            $this->render('include.js', ['sUrl' => $sUrl]) :
300
            $this->render('wrapper.js', ['sScript' => $this->getJsScript()]);
301
302
        // Wrap the js codes into HTML tags.
303
        if(count($this->aCodeJsBefore) > 0)
304
        {
305
            $sScript = implode("\n\n", $this->aCodeJsBefore);
306
            $aCodes[] = $this->render('wrapper.js', ['sScript' => $sScript]);
307
        }
308
        if(count($this->aCodeJs) > 0)
309
        {
310
            $sScript = implode("\n\n", $this->aCodeJs);
311
            $aCodes[] = $this->render('wrapper.js', ['sScript' => $sScript]);
312
        }
313
        if(count($this->aCodeJsFiles) > 0)
314
        {
315
            $aCodes[] = $this->render('includes.js', ['aUrls' => $this->aCodeJsFiles]);
316
        }
317
        if(count($this->aCodeJsAfter) > 0)
318
        {
319
            $sScript = implode("\n\n", $this->aCodeJsAfter);
320
            $aCodes[] = $this->render('wrapper.js', ['sScript' => $sScript]);
321
        }
322
        return $aCodes;
323
    }
324
325
    /**
326
     * Get the javascript code to be sent to the browser
327
     *
328
     * @param bool $bIncludeJs Also get the JS files
329
     * @param bool $bIncludeCss Also get the CSS files
330
     *
331
     * @return string
332
     * @throws UriException
333
     */
334
    public function getScript(bool $bIncludeJs, bool $bIncludeCss): string
335
    {
336
        $this->generateCodes();
337
        $aCodes = $this->renderCodes($bIncludeJs, $bIncludeCss);
338
        return implode("\n\n", $aCodes);
339
    }
340
}
341