Test Failed
Push — master ( 36c5b6...af11e6 )
by Sebastian
04:51
created

Mailcode_Commands_Command::getValidator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 11
rs 10
1
<?php
2
/**
3
 * File containing the {@see Mailcode_Commands_Command} class.
4
 *
5
 * @package Mailcode
6
 * @subpackage Commands
7
 * @see Mailcode_Commands_Command
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
use AppUtils\OperationResult;
15
16
abstract class Mailcode_Commands_Command
17
    implements
18
    Mailcode_Interfaces_Commands_Command,
19
    Mailcode_Interfaces_Commands_Validation_EmptyParams,
20
    Mailcode_Interfaces_Commands_Validation_ParamKeywords,
21
    Mailcode_Interfaces_Commands_Validation_ParseParams,
22
    Mailcode_Interfaces_Commands_Validation_TypeSupported,
23
    Mailcode_Interfaces_Commands_Validation_TypeUnsupported
24
{
25
    use Mailcode_Traits_Commands_Validation_EmptyParams;
26
    use Mailcode_Traits_Commands_Validation_ParamKeywords;
27
    use Mailcode_Traits_Commands_Validation_ParseParams;
28
    use Mailcode_Traits_Commands_Validation_TypeSupported;
29
    use Mailcode_Traits_Commands_Validation_TypeUnsupported;
30
31
    public const ERROR_NON_DUMMY_OPERATION = 46001;
32
    public const ERROR_NO_VALIDATION_RESULT_AVAILABLE = 46002;
33
    public const ERROR_MISSING_VALIDATION_METHOD = 46003;
34
    public const ERROR_MISSING_TYPE_INTERFACE = 46004;
35
    public const ERROR_LOGIC_COMMANDS_NOT_SUPPORTED = 46005;
36
    public const ERROR_URL_ENCODING_NOT_SUPPORTED = 46006;
37
    public const ERROR_NO_PARAMETERS_AVAILABLE = 46007;
38
    public const ERROR_VALIDATOR_INSTANCE_NOT_SET = 46008;
39
    
40
    public const VALIDATION_MISSING_PARAMETERS = 48301;
41
    public const VALIDATION_ADDONS_NOT_SUPPORTED = 48302;
42
    public const VALIDATION_ADDON_NOT_SUPPORTED = 48303;
43
    public const VALIDATION_UNKNOWN_COMMAND_NAME = 48304;
44
    public const VALIDATION_INVALID_PARAMS_STATEMENT = 48305;
45
46
    public const META_URL_ENCODING = 'url_encoding';
47
48
   /**
49
    * @var string
50
    */
51
    protected string $type = '';
52
53
   /**
54
    * @var string
55
    */
56
    protected string $paramsString = '';
57
    
58
   /**
59
    * @var string
60
    */
61
    protected string $matchedText = '';
62
63
   /**
64
    * @var string
65
    */
66
    protected string $hash = '';
67
    
68
   /**
69
    * @var OperationResult
70
    */
71
    protected OperationResult $validationResult;
72
    
73
   /**
74
    * @var Mailcode
75
    */
76
    protected Mailcode $mailcode;
77
    
78
   /**
79
    * @var Mailcode_Parser_Statement|NULL
80
    */
81
    protected ?Mailcode_Parser_Statement $params = null;
82
83
   /**
84
    * @var string[] 
85
    */
86
    protected array $validations = array(
87
        Mailcode_Interfaces_Commands_Validation_EmptyParams::VALIDATION_NAME_EMPTY_PARAMS,
88
        Mailcode_Interfaces_Commands_Validation_ParamKeywords::VALIDATION_NAME_KEYWORDS,
89
        Mailcode_Interfaces_Commands_Validation_ParseParams::VALIDATION_NAME_PARSE_PARAMS,
90
        Mailcode_Interfaces_Commands_Validation_TypeSupported::VALIDATION_NAME_TYPE_SUPPORTED,
91
        Mailcode_Interfaces_Commands_Validation_TypeUnsupported::VALIDATION_NAME_TYPE_UNSUPPORTED
92
    );
93
    
94
   /**
95
    * @var string
96
    */
97
    protected string $comment = '';
98
    
99
   /**
100
    * @var Mailcode_Commands_LogicKeywords|NULL
101
    */
102
    protected ?Mailcode_Commands_LogicKeywords $logicKeywords = null;
103
    
104
   /**
105
    * @var Mailcode_Parser_Statement_Validator|NULL
106
    */
107
    protected ?Mailcode_Parser_Statement_Validator $validator = null;
108
    
109
   /**
110
    * @var boolean
111
    */
112
    private bool $validated = false;
113
114
    /**
115
     * Collection of parameters for the translation backend.
116
     * @var array<string,mixed>
117
     */
118
    protected array $translationParams = array();
119
120
    /**
121
     * @var Mailcode_Commands_Command|NULL
122
     */
123
    protected ?Mailcode_Commands_Command $parent = null;
124
125
    /**
126
     * @var bool
127
     */
128
    private bool $nestingValidated = false;
129
130
    public function __construct(string $type='', string $paramsString='', string $matchedText='')
131
    {
132
        $this->type = $type;
133
        $this->paramsString = html_entity_decode($paramsString);
134
        $this->matchedText = $matchedText;
135
        $this->mailcode = Mailcode::create();
136
        $this->validationResult = new OperationResult($this);
137
        
138
        $this->init();
139
    }
140
    
141
    protected function init() : void
142
    {
143
        
144
    }
145
146
   /**
147
    * Sets the command's parent opening command, if any.
148
    * NOTE: This is set automatically by the parser, and
149
    * should not be called manually.
150
    *
151
    * @param Mailcode_Commands_Command $command
152
    */
153
    public function setParent(Mailcode_Commands_Command $command) : void
154
    {
155
        $this->parent = $command;
156
    }
157
158
    public function hasParent() : bool
159
    {
160
        return isset($this->parent);
161
    }
162
163
    public function getParent() : ?Mailcode_Commands_Command
164
    {
165
        return $this->parent;
166
    }
167
168
    /**
169
     * Whether this command has a parent command that is
170
     * a protected content command, which means it is nested
171
     * in a content block of a command.
172
     *
173
     * @return bool
174
     * @see Mailcode_Interfaces_Commands_ProtectedContent
175
     */
176
    public function hasContentParent() : bool
177
    {
178
        return isset($this->parent) && $this->parent instanceof Mailcode_Interfaces_Commands_ProtectedContent;
179
    }
180
181
    public function getID() : string
182
    {
183
        // account for commands with types: If_Variable should still return If.
184
        $base = str_replace(Mailcode_Commands_Command::class.'_', '', get_class($this));
185
        $tokens = explode('_', $base);
186
        return array_shift($tokens);
187
    }
188
189
    public function setComment(string $comment) : Mailcode_Commands_Command
190
    {
191
        $this->comment = $comment;
192
        
193
        return $this;
194
    }
195
196
    public function getComment() : string
197
    {
198
        return $this->comment;
199
    }
200
201
    public function isDummy() : bool
202
    {
203
        return $this->type === '__dummy';
204
    }
205
206
    public function getHash() : string
207
    {
208
        $this->requireNonDummy();
209
        
210
        if($this->hash === '') {
211
            $this->hash = md5($this->matchedText);
212
        }
213
        
214
        return $this->hash;
215
    }
216
    
217
    protected function requireNonDummy() : void
218
    {
219
        if(!$this->isDummy())
220
        {
221
            return;
222
        }
223
        
224
        throw new Mailcode_Exception(
225
            'Operation not allowed with dummy commands',
226
            null,
227
            self::ERROR_NON_DUMMY_OPERATION
228
        );
229
    }
230
    
231
    public function isValid() : bool
232
    {
233
        return $this->validate()->isValid();
234
    }
235
    
236
    protected function validate() : OperationResult
237
    {
238
        if(!$this->validated)
239
        {
240
            $this->requireNonDummy();
241
            $this->validateSyntax();
242
243
            $this->validated = true;
244
        }
245
        
246
        return $this->validationResult;
247
    }
248
    
249
    public function getValidationResult() :  OperationResult
250
    {
251
        return $this->validationResult;
252
    }
253
    
254
    protected function validateSyntax() : void
255
    {
256
        $validations = $this->resolveValidations();
257
258
        foreach($validations as $validation)
259
        {
260
            // break off at the first validation issue
261
            if(!$this->validateSyntaxMethod($validation))
262
            {
263
                return;
264
            }
265
        }
266
    }
267
268
    /**
269
     * @return string[]
270
     */
271
    protected function resolveValidations() : array
272
    {
273
        return array_merge($this->validations, $this->getValidations());
274
    }
275
    
276
    protected function validateSyntaxMethod(string $validation) : bool
277
    {
278
        $method = 'validateSyntax_'.$validation;
279
        
280
        if(!method_exists($this, $method))
281
        {
282
            throw new Mailcode_Exception(
283
                'Missing validation method ['.$validation.']',
284
                sprintf(
285
                    'The method [%s] is missing from class [%s].',
286
                    $method,
287
                    get_class($this)
288
                ),
289
                self::ERROR_MISSING_VALIDATION_METHOD
290
            );
291
        }
292
        
293
        $this->$method();
294
        
295
        return $this->validationResult->isValid();
296
    }
297
    
298
   /**
299
    * @return string[]
300
    */
301
    abstract protected function getValidations() : array;
302
303
    protected function _validateNesting() : void
304
    {
305
306
    }
307
308
    public function validateNesting() : OperationResult
309
    {
310
        if($this->nestingValidated)
311
        {
312
            return $this->validationResult;
313
        }
314
315
        $this->nestingValidated = true;
316
317
        $this->_validateNesting();
318
319
        return $this->validationResult;
320
    }
321
    
322
    public function hasFreeformParameters() : bool
323
    {
324
        return false;
325
    }
326
327
    public function hasType() : bool
328
    {
329
        return $this->supportsType() && !empty($this->type);
330
    }
331
    
332
    public function getType() : string
333
    {
334
        if($this->supportsType())
335
        {
336
            return $this->type;
337
        }
338
        
339
        return '';
340
    }
341
    
342
    public function hasParameters() : bool
343
    {
344
        return $this->requiresParameters() && !empty($this->paramsString);
345
    }
346
    
347
    public function getMatchedText() : string
348
    {
349
        return $this->matchedText;
350
    }
351
    
352
    public function getHighlighted() : string
353
    {
354
        if(!$this->isValid())
355
        {
356
            return '';
357
        }
358
        
359
        $highlighter = new Mailcode_Commands_Highlighter($this);
360
        return $highlighter->highlight();
361
    }
362
    
363
    public function getParamsString() : string
364
    {
365
        if($this->requiresParameters())
366
        {
367
            return $this->paramsString;
368
        }
369
        
370
        return '';
371
    }
372
    
373
    public function getParams() : ?Mailcode_Parser_Statement
374
    {
375
        return $this->params;
376
    }
377
378
    public function requireParams() : Mailcode_Parser_Statement
379
    {
380
        if(isset($this->params))
381
        {
382
            return $this->params;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->params could return the type null which is incompatible with the type-hinted return Mailcode\Mailcode_Parser_Statement. Consider adding an additional type-check to rule them out.
Loading history...
383
        }
384
385
        throw new Mailcode_Exception(
386
            'No parameters available.',
387
            'The command has no parameters.',
388
            self::ERROR_NO_PARAMETERS_AVAILABLE
389
        );
390
    }
391
    
392
    abstract public function getName() : string;
393
    
394
    abstract public function getLabel() : string;
395
    
396
    abstract public function requiresParameters() : bool;
397
    
398
    abstract public function supportsType() : bool;
399
400
    abstract public function supportsURLEncoding() : bool;
401
402
    abstract public function supportsLogicKeywords() : bool;
403
    
404
    abstract public function generatesContent() : bool;
405
406
    abstract public function getDefaultType() : string;
407
    
408
    public final function getCommandType() : string
409
    {
410
        if($this instanceof Mailcode_Commands_Command_Type_Closing)
411
        {
412
            return 'Closing';
413
        }
414
        
415
        if($this instanceof Mailcode_Commands_Command_Type_Opening)
416
        {
417
            return 'Opening';
418
        }
419
        
420
        if($this instanceof Mailcode_Commands_Command_Type_Sibling)
421
        {
422
            return 'Sibling';
423
        }
424
        
425
        if($this instanceof Mailcode_Commands_Command_Type_Standalone)
426
        {
427
            return 'Standalone';
428
        }
429
        
430
        throw new Mailcode_Exception(
431
            'Invalid command type',
432
            sprintf(
433
                'The command [%s] does not implement any of the type interfaces.',
434
                get_class($this)
435
            ),
436
            self::ERROR_MISSING_TYPE_INTERFACE
437
        );
438
    }
439
    
440
    public function getNormalized() : string
441
    {
442
        return (new Mailcode_Commands_Normalizer($this))->normalize();
443
    }
444
445
    public function getSupportedTypes() : array
446
    {
447
        return array();
448
    }
449
450
    public function getVariables() : Mailcode_Variables_Collection_Regular
451
    {
452
        return Mailcode::create()->findVariables($this->paramsString, $this);
453
    }
454
    
455
    public function __toString()
456
    {
457
        return $this->getNormalized();
458
    }
459
    
460
    public function getLogicKeywords() : Mailcode_Commands_LogicKeywords
461
    {
462
        if(isset($this->logicKeywords) && $this->supportsLogicKeywords())
463
        {
464
            return $this->logicKeywords;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->logicKeywords could return the type null which is incompatible with the type-hinted return Mailcode\Mailcode_Commands_LogicKeywords. Consider adding an additional type-check to rule them out.
Loading history...
465
        }
466
        
467
        throw new Mailcode_Exception(
468
            'Logic keywords are not supported',
469
            'Cannot retrieve the logic keywords instance: it is only available for commands supporting logic commands.',
470
            self::ERROR_LOGIC_COMMANDS_NOT_SUPPORTED
471
        );
472
    }
473
474
    public function setTranslationParam(string $name, $value)
475
    {
476
        $this->translationParams[$name] = $value;
477
        return $this;
478
    }
479
480
    public function getTranslationParam(string $name)
481
    {
482
        if(isset($this->translationParams[$name]))
483
        {
484
            return $this->translationParams[$name];
485
        }
486
487
        return null;
488
    }
489
490
    public function setURLEncoding(bool $encoding=true)
491
    {
492
        $this->requireURLEncoding();
493
494
        $this->requireParams()
495
            ->getInfo()
496
            ->setKeywordEnabled(Mailcode_Commands_Keywords::TYPE_URLENCODE, $encoding);
497
498
        return $this;
499
    }
500
501
    public function setURLDecoding(bool $decode=true)
502
    {
503
        $this->requireURLEncoding();
504
505
        $this->requireParams()
506
            ->getInfo()
507
            ->setKeywordEnabled(Mailcode_Commands_Keywords::TYPE_URLDECODE, $decode);
508
509
        return $this;
510
    }
511
512
    protected function requireURLEncoding() : void
513
    {
514
        if($this->supportsURLEncoding()) {
515
            return;
516
        }
517
518
        throw new Mailcode_Exception(
519
            'Command does not support URL encoding.',
520
            sprintf(
521
                'The command [%s] cannot use URL encoding.',
522
                get_class($this)
523
            ),
524
            self::ERROR_URL_ENCODING_NOT_SUPPORTED
525
        );
526
    }
527
528
    public function isURLEncoded() : bool
529
    {
530
        return $this->requireParams()->getInfo()->hasKeyword(Mailcode_Commands_Keywords::TYPE_URLENCODE);
531
    }
532
533
    public function isURLDecoded() : bool
534
    {
535
        return $this->requireParams()->getInfo()->hasKeyword(Mailcode_Commands_Keywords::TYPE_URLDECODE);
536
    }
537
538
    protected function getValidator() : Mailcode_Parser_Statement_Validator
539
    {
540
        if(isset($this->validator))
541
        {
542
            return $this->validator;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->validator could return the type null which is incompatible with the type-hinted return Mailcode\Mailcode_Parser_Statement_Validator. Consider adding an additional type-check to rule them out.
Loading history...
543
        }
544
545
        throw new Mailcode_Exception(
546
            'No validator available',
547
            'The validator instance has not been set.',
548
            self::ERROR_VALIDATOR_INSTANCE_NOT_SET
549
        );
550
    }
551
}
552