Passed
Push — master ( 75ee28...758f58 )
by Sebastian
04:00
created

Mailcode_Commands_Command::setURLEncoding()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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