Test Failed
Push — master ( d1e019...373b55 )
by Sebastian
02:50
created

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