Passed
Push — master ( c28222...9253d7 )
by Sebastian
02:41
created

Mailcode_Commands_Command::getMeta()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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