Passed
Push — main ( ee6fb2...96b9cc )
by Thierry
05:15
created

DialogPlugin::getLibraryHelper()   A

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
 * DialogPlugin.php - modal, alert and confirm dialogs for Jaxon.
5
 *
6
 * Show modal, alert and confirm dialogs with various javascript libraries.
7
 * This class generates js ans css code for dialog libraries.
8
 *
9
 * @package jaxon-dialogs
10
 * @author Thierry Feuzeu <[email protected]>
11
 * @copyright 2016 Thierry Feuzeu <[email protected]>
12
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
13
 * @link https://github.com/jaxon-php/jaxon-core
14
 */
15
16
namespace Jaxon\Dialogs;
17
18
use Jaxon\App\Config\ConfigListenerInterface;
19
use Jaxon\App\Config\ConfigManager;
20
use Jaxon\App\Dialog\Library\AlertInterface;
21
use Jaxon\App\Dialog\Library\ConfirmInterface;
22
use Jaxon\App\Dialog\Manager\LibraryRegistryInterface;
23
use Jaxon\App\Dialog\Library\ModalInterface;
24
use Jaxon\App\I18n\Translator;
25
use Jaxon\Config\Config;
26
use Jaxon\Dialogs\Dialog\AbstractLibrary;
27
use Jaxon\Dialogs\Dialog\LibraryHelper;
28
use Jaxon\Dialogs\Dialog\Library\Alert;
29
use Jaxon\Di\Container;
30
use Jaxon\Exception\SetupException;
31
use Jaxon\Plugin\AbstractPlugin;
32
use Jaxon\Plugin\CssCode;
33
use Jaxon\Plugin\CssCodeGeneratorInterface;
34
use Jaxon\Plugin\JsCode;
35
use Jaxon\Plugin\JsCodeGeneratorInterface;
36
use Jaxon\Utils\Template\TemplateEngine;
37
38
use function array_map;
39
use function class_implements;
40
use function count;
41
use function implode;
42
use function in_array;
43
use function is_string;
44
45
class DialogPlugin extends AbstractPlugin implements ConfigListenerInterface,
46
    LibraryRegistryInterface, CssCodeGeneratorInterface, JsCodeGeneratorInterface
47
{
48
    /**
49
     * @var string The plugin name
50
     */
51
    public const NAME = 'dialog_code';
52
53
    /**
54
     * @var array
55
     */
56
    protected $aLibraries = [];
57
58
    /**
59
     * @var array|null
60
     */
61
    protected $aActiveLibraries = null;
62
63
    /**
64
     * @var array
65
     */
66
    protected $aDefaultLibraries = [];
67
68
    /**
69
     * @var Config|null
70
     */
71
    protected $xConfig = null;
72
73
    /**
74
     * @var bool
75
     */
76
    protected $bConfigProcessed = false;
77
78
    /**
79
     * The constructor
80
     *
81
     * @param Container $di
82
     * @param Translator $xTranslator
83
     * @param ConfigManager $xConfigManager
84
     * @param TemplateEngine $xTemplateEngine
85
     */
86
    public function __construct(private Container $di, private Translator $xTranslator,
87
        private ConfigManager $xConfigManager, private TemplateEngine $xTemplateEngine)
88
    {}
89
90
    /**
91
     * @inheritDoc
92
     */
93
    public function getName(): string
94
    {
95
        return self::NAME;
96
    }
97
98
    /**
99
     * @inheritDoc
100
     */
101
    public function getHash(): string
102
    {
103
        // The version number is used as hash
104
        return '5.0.0';
105
    }
106
107
    /**
108
     * @return Config
109
     */
110
    public function config(): Config
111
    {
112
        return $this->xConfig ??= $this->xConfigManager->getConfig('dialogs');
113
    }
114
115
    /**
116
     * Register a javascript dialog library adapter.
117
     *
118
     * @param string $sClass
119
     * @param string $sLibraryName
120
     *
121
     * @return void
122
     */
123
    private function setLibraryInContainer(string $sClass, string $sLibraryName): void
124
    {
125
        if(!$this->di->h($sClass))
126
        {
127
            $this->di->set($sClass, fn($di) => $di->make($sClass));
128
        }
129
        // Set the alias, so the libraries can be found by their names.
130
        $this->di->alias("dialog_library_$sLibraryName", $sClass);
131
        // Same for the helper.
132
        $this->di->set("dialog_library_helper_$sLibraryName", fn($di) =>
133
            new LibraryHelper($di->g($sClass), $this));
134
    }
135
136
    /**
137
     * @param string $sClassName
138
     *
139
     * @return array{alert: bool, confirm: bool, modal: bool}
140
     * @throws SetupException
141
     */
142
    private function getLibraryTypes(string $sClassName): array
143
    {
144
        $aInterfaces = class_implements($sClassName);
145
        $bIsConfirm = in_array(ConfirmInterface::class, $aInterfaces);
146
        $bIsAlert = in_array(AlertInterface::class, $aInterfaces);
147
        $bIsModal = in_array(ModalInterface::class, $aInterfaces);
148
        if(!$bIsConfirm && !$bIsAlert && !$bIsModal)
149
        {
150
            // The class is invalid.
151
            $sMessage = $this->xTranslator->trans('errors.register.invalid', [
152
                'name' => $sClassName,
153
            ]);
154
            throw new SetupException($sMessage);
155
        }
156
157
        return [
158
            'confirm' => $bIsConfirm,
159
            'alert' => $bIsAlert,
160
            'modal' => $bIsModal,
161
        ];
162
    }
163
164
    /**
165
     * Register a javascript dialog library adapter.
166
     *
167
     * @param string $sClassName
168
     * @param string $sLibraryName
169
     *
170
     * @return void
171
     * @throws SetupException
172
     */
173
    public function registerLibrary(string $sClassName, string $sLibraryName): void
174
    {
175
        if(isset($this->aLibraries[$sLibraryName]))
176
        {
177
            return;
178
        }
179
180
        // Save the library
181
        $this->aLibraries[$sLibraryName] = [
182
            'name' => $sLibraryName,
183
            'active' => false,
184
            ...$this->getLibraryTypes($sClassName),
185
        ];
186
187
        // Register the library class in the container
188
        $this->setLibraryInContainer($sClassName, $sLibraryName);
189
    }
190
191
    /**
192
     * Get the dialog library
193
     *
194
     * @param string $sLibraryName
195
     *
196
     * @return AbstractLibrary|null
197
     */
198
    private function getLibrary(string $sLibraryName): AbstractLibrary|null
199
    {
200
        $sKey = "dialog_library_$sLibraryName";
201
        return $this->di->h($sKey) ? $this->di->g($sKey) : null;
202
    }
203
204
    /**
205
     * Get the dialog library helper
206
     *
207
     * @param string $sLibraryName
208
     *
209
     * @return LibraryHelper
210
     */
211
    public function getLibraryHelper(string $sLibraryName): LibraryHelper
212
    {
213
        return $this->di->g("dialog_library_helper_$sLibraryName");
214
    }
215
216
    /**
217
     * @param string $sLibraryName
218
     *
219
     * @return string
220
     */
221
222
    public function renderLibraryScript(string $sLibraryName): string
223
    {
224
        return $this->xTemplateEngine->render("jaxon::dialogs::{$sLibraryName}.js");
225
    }
226
227
    /**
228
     * @param string $sType
229
     *
230
     * @return AbstractLibrary|null
231
     */
232
    private function getDefaultLibrary(string $sType): AbstractLibrary|null
233
    {
234
        return $this->getLibrary($this->aDefaultLibraries[$sType] ?? '');
235
    }
236
237
    /**
238
     * Register the javascript dialog libraries from config options.
239
     *
240
     * @return void
241
     * @throws SetupException
242
     */
243
    private function processLibraryConfig(): void
244
    {
245
        if($this->bConfigProcessed)
246
        {
247
            return;
248
        }
249
250
        // Register the 3rd party libraries
251
        $aLibraries = $this->config()->getOption('lib.ext', []);
252
        foreach($aLibraries as $sLibraryName => $sClassName)
253
        {
254
            $this->registerLibrary($sClassName, $sLibraryName);
255
        }
256
257
        // Set the other libraries in use
258
        $aLibraries = $this->config()->getOption('lib.use', []);
259
        foreach($aLibraries as $sLibraryName)
260
        {
261
            if(isset($this->aLibraries[$sLibraryName])) // Make sure the library exists
262
            {
263
                $this->aLibraries[$sLibraryName]['active'] = true;
264
            }
265
        }
266
267
        // Set the default alert, modal and confirm libraries.
268
        foreach(['alert', 'modal', 'confirm'] as $sType)
269
        {
270
            $sLibraryName = trim($this->config()->getOption("default.$sType", ''));
271
            if(!is_string($sLibraryName) || $sLibraryName === '')
272
            {
273
                continue;
274
            }
275
276
            if(!($this->aLibraries[$sLibraryName][$sType] ?? false))
277
            {
278
                $sMessage = $this->xTranslator->trans('errors.dialog.library', [
279
                    'type' => $sType,
280
                    'name' => $sLibraryName,
281
                ]);
282
                throw new SetupException($sMessage);
283
            }
284
285
            $this->aLibraries[$sLibraryName]['active'] = true;
286
            $this->aDefaultLibraries[$sType] = $sLibraryName;
287
        }
288
289
        $this->bConfigProcessed = true;
290
    }
291
292
    /**
293
     * @inheritDoc
294
     */
295
    public function getConfirmLibrary(): ConfirmInterface
296
    {
297
        $this->processLibraryConfig();
298
299
        return $this->getDefaultLibrary('confirm') ?? $this->di->g(Alert::class);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDefault...g\Library\Alert::class) could return the type Jaxon\Dialogs\Dialog\AbstractLibrary which is incompatible with the type-hinted return Jaxon\App\Dialog\Library\ConfirmInterface. Consider adding an additional type-check to rule them out.
Loading history...
300
    }
301
302
    /**
303
     * @inheritDoc
304
     */
305
    public function getAlertLibrary(): AlertInterface
306
    {
307
        $this->processLibraryConfig();
308
309
        return $this->getDefaultLibrary('alert') ?? $this->di->g(Alert::class);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDefault...g\Library\Alert::class) could return the type Jaxon\Dialogs\Dialog\AbstractLibrary which is incompatible with the type-hinted return Jaxon\App\Dialog\Library\AlertInterface. Consider adding an additional type-check to rule them out.
Loading history...
310
    }
311
312
    /**
313
     * @inheritDoc
314
     */
315
    public function getModalLibrary(): ?ModalInterface
316
    {
317
        $this->processLibraryConfig();
318
319
        return $this->getDefaultLibrary('modal');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDefaultLibrary('modal') could return the type Jaxon\Dialogs\Dialog\AbstractLibrary which is incompatible with the type-hinted return Jaxon\App\Dialog\Library\ModalInterface|null. Consider adding an additional type-check to rule them out.
Loading history...
320
    }
321
322
    /**
323
     * @return array<AbstractLibrary>
324
     */
325
    private function getActiveLibraries(): array
326
    {
327
        if($this->aActiveLibraries !== null)
328
        {
329
            return $this->aActiveLibraries;
330
        }
331
332
        $this->processLibraryConfig();
333
334
        // Set the active libraries.
335
        $cFilter = fn(array $aLibrary) => $aLibrary['active'];
336
        $cGetter = fn(array $aLibrary) => $this->getLibrary($aLibrary['name']);
337
        $aLibraries = array_filter($this->aLibraries, $cFilter);
338
        return $this->aActiveLibraries = array_map($cGetter, $aLibraries);
339
    }
340
341
    /**
342
     * @return string
343
     */
344
    private function getConfigScript(): string
345
    {
346
        $aOptions = [
347
            'labels' => $this->xTranslator->translations('labels'),
348
            'defaults' => $this->config()->getOption('default', []),
349
        ];
350
        $aLibrariesOptions = [];
351
        foreach($this->getActiveLibraries() as $xLibrary)
352
        {
353
            $aLibOptions = $xLibrary->getJsOptions();
354
            if(count($aLibOptions) > 0)
355
            {
356
                $aLibrariesOptions[$xLibrary->getName()] = $aLibOptions;
357
            }
358
        }
359
        if(count($aLibrariesOptions) > 0)
360
        {
361
            $aOptions['options'] = $aLibrariesOptions;
362
        }
363
364
        return 'jaxon.dom.ready(() => jaxon.dialog.config(' . json_encode($aOptions) . '));';
365
    }
366
367
    /**
368
     * @inheritDoc
369
     */
370
    public function getCssCode(): CssCode
371
    {
372
        $aUrls = [];
373
        $aCodes = [];
374
        foreach($this->getActiveLibraries() as $xLibrary)
375
        {
376
            $aUrls = [...$aUrls, ...$xLibrary->getCssUrls()];
377
            if(($sCode = $xLibrary->getCssCode()) !== '')
378
            {
379
                $aCodes[] = $sCode;
380
            }
381
        }
382
383
        return new CssCode(implode("\n", $aCodes), $aUrls);
384
    }
385
386
    /**
387
     * @inheritDoc
388
     */
389
    public function getJsCode(): JsCode
390
    {
391
        $sCodeBefore = $this->getConfigScript();
392
        $aUrls = [];
393
        $aCodes = [];
394
        foreach($this->getActiveLibraries() as $xLibrary)
395
        {
396
            $aUrls = [...$aUrls, ...$xLibrary->getJsUrls()];
397
            if(($sCode = $xLibrary->getJsCode()) !== '')
398
            {
399
                $aCodes[] = $sCode;
400
            }
401
        }
402
403
        return new JsCode(implode("\n", $aCodes), $aUrls, $sCodeBefore);
404
    }
405
406
    /**
407
     * @inheritDoc
408
     */
409
    public function onChange(Config $xConfig, string $sName): void
410
    {
411
        // Reset all the config related data on config change.
412
        $this->xConfig = null;
413
        $this->aActiveLibraries = null;
414
        $this->aDefaultLibraries = [];
415
        $this->bConfigProcessed = false;
416
    }
417
}
418