Test Setup Failed
Push — master ( e4f8c2...f9e497 )
by Sebastian
03:34
created

Mailcode_Commands_Command::getHighlighted()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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