Passed
Branch dev (6bb8f6)
by Dispositif
03:01
created

AbstractWikiTemplate::serialize()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 45
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 24
c 0
b 0
f 0
nc 16
nop 1
dl 0
loc 45
rs 8.0555
1
<?php
2
/**
3
 * This file is part of dispositif/wikibot application
4
 * 2019 : Philippe M. <[email protected]>
5
 * For the full copyright and MIT license information, please view the LICENSE file.
6
 */
7
8
declare(strict_types=1);
9
10
namespace App\Domain\Models\Wiki;
11
12
use App\Domain\Utils\ArrayProcessTrait;
13
use App\Domain\Utils\TemplateParser;
14
use App\Domain\Utils\WikiTextUtil;
15
use DomainException;
16
use Exception;
17
use Throwable;
18
19
/**
20
 * TODO detect userPreferences (inlineStyle, spaceStyle...)
21
 * Class AbstractWikiTemplate.
22
 */
23
abstract class AbstractWikiTemplate extends AbstractParametersObject
24
{
25
    use ArrayProcessTrait, InfoTrait;
26
27
    const MODEL_NAME = '';
28
29
    /**
30
     * Error in wiki parsing without those required params.
31
     */
32
    const EDIT_REQUIRED_PARAMETERS = [];
33
    /**
34
     * The minimum parameters for pretty wiki-template.
35
     */
36
    const MINIMUM_PARAMETERS = [];
37
38
    /* commented to allow inherit from Interface in OuvrageTemplate
39
       const PARAM_ALIAS = []; */
40
41
    const COMMENT_STRIPPED = '<!-- Paramètre obligatoire -->';
42
43
    public $log = [];
44
45
    public $parametersErrorFromHydrate;
46
47
    public $userSeparator; // todo move to WikiRef
48
    /**
49
     * @var bool
50
     */
51
    public $userMultiSpaced = false;
52
53
    /**
54
     * optional
55
     * Not a constant so it can be modified in constructor.
56
     * Commented so it can be inherit from trait in OuvrageTemplate
57
     */
58
    //protected $parametersByOrder = [];
59
60
    protected $paramOrderByUser = [];
61
    /**
62
     * @var bool
63
     */
64
65
66
    /**
67
     * AbstractWikiTemplate constructor.
68
     *
69
     * @throws Exception
70
     */
71
    public function __construct()
72
    {
73
        if (empty(static::MINIMUM_PARAMETERS)) {
0 ignored issues
show
introduced by
The condition empty(static::MINIMUM_PARAMETERS) is always true.
Loading history...
74
            throw new Exception(sprintf('DEFAULT_PARAMETERS not configured in "%s"', get_called_class()));
75
        }
76
        $this->parametersValues = static::MINIMUM_PARAMETERS;
77
78
        if (empty($this->parametersByOrder)) {
79
            $this->parametersByOrder = static::MINIMUM_PARAMETERS;
80
        }
81
    }
82
83
    /**
84
     * Verify the required template parameters for an edit by the bot.
85
     *
86
     * @return bool
87
     */
88
    public function isValidForEdit(): bool
89
    {
90
        $validParams = array_keys(static::MINIMUM_PARAMETERS);
91
        if (!empty(static::EDIT_REQUIRED_PARAMETERS)) {
0 ignored issues
show
introduced by
The condition empty(static::EDIT_REQUIRED_PARAMETERS) is always true.
Loading history...
92
            $validParams = static::EDIT_REQUIRED_PARAMETERS;
93
        }
94
95
        foreach ($validParams as $param) {
96
            if (in_array($param, ['date', 'année'])
97
                && ($this->hasParamValue('date') || $this->hasParamValue('année'))
98
            ) {
99
                // équivalence date-année
100
                continue;
101
            }
102
            if (!$this->hasParamValue($param)) {
103
                return false;
104
            }
105
        }
106
107
        return true;
108
    }
109
110
    /**
111
     * Get data from wiki-template. Also invalid param/values.
112
     *
113
     * @return array
114
     */
115
    public function toArray(): array
116
    {
117
        $allValue = array_merge($this->parametersValues, $this->parametersErrorFromHydrate ?? []);
118
119
        return $this->deleteEmptyValueArray($allValue);
120
    }
121
122
    /**
123
     * Is the parameter's name valid ?
124
     *
125
     * @param string $paramName
126
     *
127
     * @return bool
128
     */
129
    public function isParamOrAlias(string $paramName): bool
130
    {
131
        return in_array($paramName, $this->getParamsAndAlias());
132
    }
133
134
    public function getParamsAndAlias(): array
135
    {
136
        return array_merge($this->parametersByOrder, array_keys(static::PARAM_ALIAS));
137
        // todo : $this::PARAM_ALIAS or static::PARAM_ALIAS (traits?) ?
138
    }
139
140
    /**
141
     * TODO check if method set{ParamName} exists.
142
     *
143
     * @param string $name
144
     * @param string $value
145
     *
146
     * @return AbstractParametersObject
147
     * @throws Exception
148
     */
149
    public function setParam(string $name, string $value): AbstractParametersObject
150
    {
151
        try {
152
            $this->checkParamName($name);
153
        } catch (Throwable $e) {
154
            $this->log[] = sprintf('no parameter "%s" in AbstractParametersObject "%s"', $name, get_called_class());
155
156
            return $this;
157
        }
158
159
        $name = $this->getAliasParam($name);
160
        $value = trim($value);
161
        if (!empty($value) || $this->parametersValues[$name]) {
162
            $this->parametersValues[$name] = $value;
163
        }
164
165
        return $this;
166
    }
167
168
    /**
169
     * TODO return bool + log() ?
170
     * todo check keyNum <= count($parametersByOrder).
171
     *
172
     * @param $name string|int
173
     *
174
     * @throws Exception
175
     */
176
    protected function checkParamName($name): void
177
    {
178
        // todo verify/useless ?
179
        if (is_int($name)) {
180
            $name = (string)$name;
181
        }
182
183
        // that parameter exists in template ?
184
        if (in_array($name, $this->parametersByOrder)
185
            || array_key_exists($name, static::PARAM_ALIAS)
186
        ) {
187
            return;
188
        }
189
190
        // keyNum parameter ?
191
        //        if (!in_array($name, ['1', '2', '3', '4'])) {
192
        throw new Exception(sprintf('no parameter "%s" in template "%s"', $name, get_called_class()));
193
    }
194
195
    /**
196
     * @param string $name
197
     *
198
     * @return string
199
     */
200
    public function getAliasParam(string $name): string
201
    {
202
        if (array_key_exists($name, static::PARAM_ALIAS)) {
203
            $name = static::PARAM_ALIAS[$name];
204
        }
205
206
        return $name;
207
    }
208
209
    /**
210
     * @param $param
211
     *
212
     * @return string|null
213
     * @throws Exception
214
     */
215
    public function __get($param): ?string
216
    {
217
        $this->checkParamName($param);
218
219
        if (!empty($this->parametersValues[$param])) {
220
            return $this->parametersValues[$param];
221
        }
222
223
        // todo param_alias ?
224
        return null;
225
    }
226
227
    public function unsetParam(string $name): void
228
    {
229
        $this->checkParamName($name);
230
        $name = $this->getAliasParam($name);
231
        unset($this->parametersValues[$name]);
232
    }
233
234
    /**
235
     * TODO move/refac.
236
     *
237
     * @param string $tplText
238
     *
239
     * @throws Exception
240
     */
241
    public function hydrateFromText(string $tplText)
242
    {
243
        $tplText = str_ireplace(static::COMMENT_STRIPPED, '', $tplText);
244
245
        if (WikiTextUtil::isCommented($tplText)) {
246
            throw new DomainException('HTML comment tag detected');
247
        }
248
        $data = TemplateParser::parseDataFromTemplate($this::MODEL_NAME, $tplText);
249
        $this->detectUserSeparator($tplText);
250
        $this->hydrate($data);
251
    }
252
253
    public function detectUserSeparator($text): void
254
    {
255
        $this->userSeparator = TemplateParser::findUserStyleSeparator($text);
256
        $this->userMultiSpaced = TemplateParser::isMultispacedTemplate($text);
257
    }
258
259
    /**
260
     * @param array     $data
261
     * @param bool|null $noError mode strict
262
     *
263
     * @return AbstractWikiTemplate
264
     * @throws Exception
265
     */
266
    public function hydrate(array $data, ?bool $noError = false): self
267
    {
268
        foreach ($data as $name => $value) {
269
            if (is_string($value)) {
270
                $this->hydrateTemplateParameter($name, $value, $noError);
271
            }
272
        }
273
274
        $this->setParamOrderByUser($data);
275
276
        return $this;
277
    }
278
279
    /**
280
     * @param           $name    string|int
281
     * @param string    $value
282
     * @param bool|null $noError mode strict
283
     */
284
    protected function hydrateTemplateParameter($name, string $value, ?bool $noError = false): void
285
    {
286
        // Gestion alias
287
        try {
288
            $this->checkParamName($name);
289
            $name = $this->getAliasParam($name); // main parameter name
290
291
            // Gestion des doublons de paramètres
292
            if ($this->hasParamValue($name)) {
293
                if (!empty($value)) {
294
                    $this->log[] = "parameter $name en doublon";
295
                    $this->parametersErrorFromHydrate[$name.'-doublon'] = $value;
296
                }
297
298
                return;
299
            }
300
        } catch (Throwable $e) {
301
            unset($e);
302
            // hack : 1 => "ouvrage collectif"
303
            $name = (string)$name;
304
            $this->log[] = "parameter $name not found";
305
            if (!$noError) {
306
                $this->parametersErrorFromHydrate[$name] = $value;
307
            }
308
309
            return;
310
        }
311
312
313
        if (empty($value)) {
314
            // optional parameter
315
            if (!isset(static::MINIMUM_PARAMETERS[$name])) {
316
                unset($this->parametersValues[$name]);
317
318
                return;
319
            }
320
            // required parameter
321
            $this->parametersValues[$name] = '';
322
        }
323
324
        $method = $this->setterMethodName($name);
325
        if (method_exists($this, $method)) {
326
            $this->$method($value);
327
328
            return;
329
        }
330
331
        $this->parametersValues[$name] = $value;
332
    }
333
334
    /**
335
     * For a parameter, check is the value exists (not empty).
336
     *
337
     * @param string $name
338
     *
339
     * @return bool
340
     */
341
    public function hasParamValue(string $name): bool
342
    {
343
        try {
344
            if (!empty(trim($this->getParam($name)))) {
345
                return true;
346
            }
347
        } catch (Throwable $e) {
348
            unset($e);
349
        }
350
351
        return false;
352
    }
353
354
    /**
355
     * @param string $name
356
     *
357
     * @return string|null
358
     * @throws Exception
359
     */
360
    public function getParam(string $name): ?string
361
    {
362
        try {
363
            $this->checkParamName($name);
364
        } catch (Exception $e) {
365
            return null;
366
        }
367
        $name = $this->getAliasParam($name);
368
369
        return ($this->parametersValues[$name]) ?? null;
370
    }
371
372
    /**
373
     * Define the serialize order of parameters (from user initial choice).
374
     * default : $params = ['param1'=>'', 'param2' => '', ...]
375
     * OK with $params = ['a','b','c'].
376
     *
377
     * @param array
378
     *
379
     * @throws Exception
380
     */
381
    public function setParamOrderByUser(array $params = []): void
382
    {
383
        $validParams = [];
384
        foreach ($params as $key => $value) {
385
            $name = (is_int($key)) ? $value : $key;
386
387
            try {
388
                $this->checkParamName($name);
389
                $name = $this->getAliasParam($name);
390
                $validParams[] = $name;
391
            } catch (Throwable $e) {
392
                unset($e);
393
                $this->log[] = "Parameter $name do not exists";
394
395
                continue;
396
            }
397
        }
398
        $this->paramOrderByUser = $validParams;
399
    }
400
401
    /**
402
     * TODO : data transfer object (DTO) to mix userErrorParam data ?
403
     * TODO : refac $inlineStyle as $userPreferences[] and bool flag on serialize().
404
     *
405
     * @param bool|null $cleanOrder
406
     *
407
     * @return string
408
     */
409
    public function serialize(?bool $cleanOrder = false): string
410
    {
411
        $paramsByRenderOrder = $this->paramsByRenderOrder($cleanOrder);
412
        $paramsByRenderOrder = $this->filterEmptyNotRequired($paramsByRenderOrder);
413
414
        // max caractères des paramètres (valides)
415
        $maxChars = 0;
416
        foreach (array_keys($paramsByRenderOrder) as $paramName) {
417
            $maxChars = max($maxChars, mb_strlen($paramName));
418
        }
419
420
        // TODO : $option to add or not the wrong parameters ?
421
        // Using the wrong parameters+value from user input ?
422
        $paramsByRenderOrder = $this->mergeWrongParametersFromUser($paramsByRenderOrder);
423
424
        $string = '{{'.static::MODEL_NAME;
425
        foreach ($paramsByRenderOrder as $paramName => $paramValue) {
426
            $string .= ($this->userSeparator) ?? '|';
427
428
            if (!in_array($paramName, ['0', '1', '2', '3', '4', '5'])) {
429
                $string .= $paramName;
430
431
                // MultiSpaced : espacements multiples pour style étendu : "auteur    = Bla"
432
                if ($this->userSeparator
433
                    && false !== strpos($this->userSeparator, "\n")
434
                    && $this->userMultiSpaced
435
                ) {
436
                    $spaceNb = max(0, $maxChars - mb_strlen($paramName));
437
                    $string .= str_repeat(' ', $spaceNb);
438
                    $string .= ' = ';
439
                } else {
440
                    // style condensé "auteur=Bla" ou non multiSpaced
441
                    $string .= '=';
442
                }
443
            }
444
            // {{template|1=blabla}} -> {{template|blabla}}
445
            $string .= $paramValue;
446
        }
447
        // expanded model -> "\n}}"
448
        if ($this->userSeparator && false !== strpos($this->userSeparator, "\n")) {
449
            $string .= "\n";
450
        }
451
        $string .= '}}';
452
453
        return $string;
454
    }
455
456
    /**
457
     * @param bool|null $cleanOrder
458
     *
459
     * @return array
460
     */
461
    protected function paramsByRenderOrder(?bool $cleanOrder = false): array
462
    {
463
        $renderParams = [];
464
465
        // By user order
466
        if (!empty($this->paramOrderByUser) && !$cleanOrder) {
467
            $completeFantasyOrder = $this->completeFantasyOrder(
468
                $this->paramOrderByUser,
469
                $this->parametersByOrder
470
            );
471
472
            foreach ($completeFantasyOrder as $paramName) {
473
                if (isset($this->parametersValues[$paramName])) {
474
                    $renderParams[$paramName] = $this->parametersValues[$paramName];
475
                }
476
            }
477
478
            return $renderParams;
479
        }
480
481
        // default order
482
        foreach ($this->parametersByOrder as $order => $paramName) {
483
            if (isset($this->parametersValues[$paramName])) {
484
                $renderParams[$paramName] = $this->parametersValues[$paramName];
485
            }
486
        }
487
488
        return $renderParams;
489
    }
490
491
    /**
492
     * Delete key if empty value and the key not required.
493
     *
494
     * @param array $params
495
     *
496
     * @return array
497
     */
498
    protected function filterEmptyNotRequired(array $params): array
499
    {
500
        $render = [];
501
        foreach ($params as $name => $value) {
502
            if (empty($value) && !isset(static::MINIMUM_PARAMETERS[$name])) {
503
                continue;
504
            }
505
            $render[$name] = $params[$name];
506
        }
507
508
        return $render;
509
    }
510
511
    /**
512
     * Merge Render data with wrong parameters+value from user input.
513
     * The wrong ones already corrected are not added.
514
     *
515
     * @param array $paramsByRenderOrder
516
     *
517
     * @return array
518
     */
519
    protected function mergeWrongParametersFromUser(array $paramsByRenderOrder): array
520
    {
521
        if (!empty($this->parametersErrorFromHydrate)) {
522
            // FIXED? : si y'a de l'info dans un paramètre erreur et sans value...
523
            //$errorUserData = $this->deleteEmptyValueArray($this->parametersErrorFromHydrate);
524
            $errorUserData = $this->parametersErrorFromHydrate;
525
526
            // Add a note in HTML commentary
527
            foreach ($errorUserData as $param => $value) {
528
                if ('string' === gettype($param) && empty(trim($param))) {
529
                    continue;
530
                }
531
                if (is_int($param)) {
532
                    // erreur "|lire en ligne|"
533
                    if (in_array($value, $this->getParamsAndAlias())) {
534
                        unset($errorUserData[$param]);
535
536
                        continue;
537
                    }
538
539
                    // ou 1= 2= 3=
540
                    $errorUserData[$param] = $value.' <!--VALEUR SANS NOM DE PARAMETRE -->';
541
542
                    continue;
543
                }
544
                $errorUserData[$param] = $value." <!--PARAMETRE '$param' N'EXISTE PAS -->";
545
            }
546
            $paramsByRenderOrder = array_merge($paramsByRenderOrder, $errorUserData);
547
        }
548
549
        return $paramsByRenderOrder;
550
    }
551
}
552