Passed
Push — master ( eafb24...c53bf4 )
by Sebastian
04:30
created

Mailcode_Parser_Safeguard::selectFormatter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 11
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 19
rs 9.9
1
<?php
2
/**
3
 * File containing the {@see Mailcode_Parser_Safeguard} class.
4
 *
5
 * @package Mailcode
6
 * @subpackage Parser
7
 * @see Mailcode_Parser_Safeguard
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
/**
15
 * Command safeguarder: used to replace the mailcode commands
16
 * in a string with placeholders, to allow safe text transformation
17
 * and filtering operations on strings, without risking to break 
18
 * any of the contained commands (if any).
19
 * 
20
 * Usage:
21
 * 
22
 * <pre>
23
 * $safeguard = Mailcode::create()->createSafeguard($sourceString);
24
 * 
25
 * // replace all commands with placeholders
26
 * $workString = $safeguard->makeSafe();
27
 * 
28
 * // dome something with the work string - filtering, parsing...
29
 * 
30
 * // restore all command placeholders
31
 * $resultString = $safeguard->makeWhole($workString);
32
 * </pre>
33
 * 
34
 * Note that by default, the placeholders are delimited with
35
 * two underscores, e.g. <code>__PCH0001__</code>. If the text
36
 * transformations include replacing or modifying underscores,
37
 * you should use a different delimiter:
38
 * 
39
 * <pre>
40
 * $safeguard = Mailcode::create()->createSafeguard($sourceString);
41
 * 
42
 * // change the delimiter to %%. Can be any arbitrary string.
43
 * $safeguard->setDelimiter('%%');
44
 * </pre>
45
 *
46
 * @package Mailcode
47
 * @subpackage Parser
48
 * @author Sebastian Mordziol <[email protected]>
49
 */
50
class Mailcode_Parser_Safeguard
51
{
52
    const ERROR_INVALID_COMMANDS = 47801;
53
    
54
    const ERROR_COMMAND_PLACEHOLDER_MISSING = 47802;
55
    
56
    const ERROR_EMPTY_DELIMITER = 47803;
57
    
58
    const ERROR_PLACEHOLDER_NOT_FOUND = 47804;
59
    
60
    const ERROR_UNKNOWN_FORMATTER = 47805;
61
    
62
    const ERROR_NOT_A_SINGLE_LINES_FORMATTER = 47806;
63
    
64
   /**
65
    * @var Mailcode_Parser
66
    */
67
    protected $parser;
68
    
69
   /**
70
    * @var Mailcode_Collection
71
    */
72
    protected $commands;
73
    
74
   /**
75
    * @var string
76
    */
77
    protected $originalString;
78
    
79
   /**
80
    * @var Mailcode_Collection
81
    */
82
    protected $collection;
83
    
84
   /**
85
    * Counter for the placeholders, global for all placeholders.
86
    * @var integer
87
    */
88
    private static $counter = 0;
89
    
90
   /**
91
    * @var Mailcode_Parser_Safeguard_Placeholder[]
92
    */
93
    protected $placeholders;
94
    
95
   /**
96
    * @var string
97
    */
98
    protected $delimiter = '__';
99
    
100
   /**
101
    * @var string[]|NULL
102
    */
103
    protected $placeholderStrings;
104
    
105
   /**
106
    * @var Mailcode_Parser_Safeguard_Formatter
107
    */
108
    protected $formatter;
109
    
110
    public function __construct(Mailcode_Parser $parser, string $subject)
111
    {
112
        $this->parser = $parser;
113
        $this->originalString = $subject;
114
    }
115
    
116
   /**
117
    * Retrieves the string the safeguard was created for.
118
    * 
119
    * @return string
120
    */
121
    public function getOriginalString() : string
122
    {
123
        return $this->originalString;
124
    }
125
    
126
   /**
127
    * Sets the delimiter character sequence used to prepend
128
    * and append to the placeholders.
129
    * 
130
    * The delimiter's default is "__" (two underscores).
131
    * 
132
    * @param string $delimiter
133
    * @return Mailcode_Parser_Safeguard
134
    */
135
    public function setDelimiter(string $delimiter) : Mailcode_Parser_Safeguard
136
    {
137
        if(empty($delimiter))
138
        {
139
            throw new Mailcode_Exception(
140
                'Empty delimiter',
141
                'Delimiters may not be empty.',
142
                self::ERROR_EMPTY_DELIMITER
143
            );
144
        }
145
        
146
        $this->delimiter = $delimiter;
147
        
148
        return $this;
149
    }
150
    
151
    public function getDelimiter() : string
152
    {
153
        return $this->delimiter;
154
    }
155
    
156
   /**
157
    * Retrieves the safe string in which all commands have been replaced
158
    * by placeholder strings.
159
    *
160
    * @return string
161
    * @throws Mailcode_Exception 
162
    *
163
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
164
    */
165
    public function makeSafe() : string
166
    {
167
        $this->requireValidCollection();
168
        
169
        $replaces = $this->getReplaces();
170
        
171
        $safe = str_replace(array_values($replaces), array_keys($replaces), $this->originalString);
172
173
        // If a formatter has been selected, let it modify the string.
174
        if(isset($this->formatter))
175
        {
176
            $safe = $this->formatter->format($safe);
177
        }
178
        
179
        return $safe;
180
    }
181
    
182
    public function selectFormatter(string $formatterID) : Mailcode_Parser_Safeguard_Formatter
183
    {
184
        $class = 'Mailcode\Mailcode_Parser_Safeguard_Formatter_'.$formatterID;
185
        
186
        if(class_exists($class))
187
        {
188
            $this->formatter = new $class($this);
189
            
190
            return $this->formatter;
191
        }
192
        
193
        throw new Mailcode_Exception(
194
            'Unknown safeguard formatter.',
195
            sprintf(
196
                'The formatter [%s] does not exist, could not find class [%s].',
197
                $formatterID,
198
                $class
199
            ),
200
            self::ERROR_UNKNOWN_FORMATTER
201
        );
202
    }
203
    
204
   /**
205
    * Enables the formatter that ensures that all commands that
206
    * @return Mailcode_Parser_Safeguard_Formatter_SingleLines
207
    */
208
    public function selectSingleLinesFormatter() : Mailcode_Parser_Safeguard_Formatter_SingleLines
209
    {
210
        $formatter = $this->selectFormatter('SingleLines');
211
        
212
        if($formatter instanceof Mailcode_Parser_Safeguard_Formatter_SingleLines)
0 ignored issues
show
introduced by
$formatter is always a sub-type of Mailcode\Mailcode_Parser...d_Formatter_SingleLines.
Loading history...
213
        {
214
            return $formatter;
215
        }
216
        
217
        throw new Mailcode_Exception(
218
            'Not the expected formatter',
219
            sprintf(
220
                'The formatter is not an instance of [%s].',
221
                Mailcode_Parser_Safeguard_Formatter_SingleLines::class
222
            ),
223
            self::ERROR_NOT_A_SINGLE_LINES_FORMATTER
224
        );
225
    }
226
    
227
   /**
228
    * Retrieves an associative array with pairs of
229
    * [placeholder string => replacement text]. 
230
    * 
231
    * @param bool $highlighted
232
    * @return string[]string
233
    */
234
    protected function getReplaces(bool $highlighted=false) : array
235
    {
236
        $placeholders = $this->getPlaceholders();
237
        
238
        $replaces = array();
239
        
240
        foreach($placeholders as $placeholder)
241
        {
242
            $replace = '';
243
            
244
            if($highlighted)
245
            {
246
                $replace = $placeholder->getHighlightedText();
247
            }
248
            else 
249
            {
250
                $replace = $placeholder->getOriginalText();
251
            }
252
            
253
            $replaces[$placeholder->getReplacementText()] = $replace;
254
        }
255
        
256
        return $replaces;
257
    }
258
    
259
    
260
   /**
261
    * Retrieves all placeholders that have to be added to
262
    * the subject text.
263
    * 
264
    * @return \Mailcode\Mailcode_Parser_Safeguard_Placeholder[]
265
    */
266
    public function getPlaceholders()
267
    {
268
        if(isset($this->placeholders))
269
        {
270
            return $this->placeholders;
271
        }
272
        
273
        $this->placeholders = array();
274
        
275
        $cmds = $this->getCollection()->getGroupedByHash();
276
        
277
        foreach($cmds as $command)
278
        {
279
            self::$counter++;
280
            
281
            $this->placeholders[] = new Mailcode_Parser_Safeguard_Placeholder(
282
                self::$counter,
283
                $command,
284
                $this
285
            );
286
        }
287
288
        return $this->placeholders;
289
    }
290
    
291
    protected function restore(string $string, bool $highlighted=false) : string
292
    {
293
        $this->requireValidCollection();
294
        
295
        $replaces = $this->getReplaces($highlighted);
296
        
297
        $placeholderStrings = array_keys($replaces);
298
        
299
        foreach($placeholderStrings as $search)
300
        {
301
            if(!strstr($string, $search))
302
            {
303
                throw new Mailcode_Exception(
304
                    'Command placeholder not found',
305
                    sprintf(
306
                        'A placeholder for a command could not be found in the string to restore: [%s].',
307
                        $search
308
                    ),
309
                    self::ERROR_COMMAND_PLACEHOLDER_MISSING
310
                );
311
            }
312
        }
313
        
314
        return str_replace($placeholderStrings, array_values($replaces), $string);
315
    }
316
    
317
   /**
318
    * Makes the string whole again after transforming or filtering it,
319
    * by replacing the command placeholders with the original commands.
320
    *
321
    * @param string $string
322
    * @return string
323
    * @throws Mailcode_Exception
324
    *
325
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
326
    * @see Mailcode_Parser_Safeguard::ERROR_COMMAND_PLACEHOLDER_MISSING
327
    */
328
    public function makeWhole(string $string) : string
329
    {
330
        return $this->restore($string, false);
331
    }
332
333
   /**
334
    * Like makeWhole(), but replaces the commands with a syntax
335
    * highlighted version, meant for human readable texts only.
336
    * 
337
    * Note: the commands lose their functionality (They cannot be 
338
    * parsed from that string again).
339
    *
340
    * @param string $string
341
    * @return string
342
    * @throws Mailcode_Exception
343
    *
344
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
345
    * @see Mailcode_Parser_Safeguard::ERROR_COMMAND_PLACEHOLDER_MISSING
346
    */
347
    public function makeHighlighted(string $string) : string
348
    {
349
        return $this->restore($string, true);
350
    }
351
    
352
   /**
353
    * Retrieves the commands collection contained in the string.
354
    * 
355
    * @return Mailcode_Collection
356
    */
357
    public function getCollection() : Mailcode_Collection
358
    {
359
        if(isset($this->collection))
360
        {
361
            return $this->collection;
362
        }
363
        
364
        $this->collection = $this->parser->parseString($this->originalString);
365
        
366
        return $this->collection;
367
    }
368
    
369
    public function isValid() : bool
370
    {
371
        return $this->getCollection()->isValid();
372
    }
373
    
374
   /**
375
    * @throws Mailcode_Exception
376
    * 
377
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
378
    */
379
    protected function requireValidCollection() : void
380
    {
381
        if($this->getCollection()->isValid())
382
        {
383
            return;
384
        }
385
        
386
        throw new Mailcode_Exception(
387
            'Cannot safeguard invalid commands',
388
            sprintf(
389
                'The collection contains invalid commands. Safeguarding is only allowed with valid commands.'.
390
                'Source string: [%s]',
391
                $this->originalString
392
            ),
393
            self::ERROR_INVALID_COMMANDS
394
        );
395
    }
396
    
397
   /**
398
    * Retrieves a list of all placeholder IDs used in the text.
399
    * 
400
    * @return string[]
401
    */
402
    public function getPlaceholderStrings() : array
403
    {
404
        if(isset($this->placeholderStrings))
405
        {
406
            return $this->placeholderStrings;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->placeholderStrings could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
407
        }
408
        
409
        $placeholders = $this->getPlaceholders();
410
        
411
        $this->placeholderStrings = array();
412
        
413
        foreach($placeholders as $placeholder)
414
        {
415
            $this->placeholderStrings[] = $placeholder->getReplacementText();
416
        }
417
        
418
        return $this->placeholderStrings;
419
    }
420
    
421
    public function isPlaceholder(string $subject) : bool
422
    {
423
        $ids = $this->getPlaceholderStrings();
424
        
425
        return in_array($subject, $ids);
426
    }
427
    
428
   /**
429
    * Retrieves a placeholder instance by its ID.
430
    * 
431
    * @param int $id
432
    * @throws Mailcode_Exception If the placeholder was not found.
433
    * @return Mailcode_Parser_Safeguard_Placeholder
434
    */
435
    public function getPlaceholderByID(int $id) : Mailcode_Parser_Safeguard_Placeholder
436
    {
437
        $placeholders = $this->getPlaceholders();
438
        
439
        foreach($placeholders as $placeholder)
440
        {
441
            if($placeholder->getID() === $id)
442
            {
443
                return $placeholder;
444
            }
445
        }
446
        
447
        throw new Mailcode_Exception(
448
            'No such safeguard placeholder.',
449
            sprintf(
450
                'The placeholder ID [%s] is not present in the safeguard instance.',
451
                $id
452
            ),
453
            self::ERROR_PLACEHOLDER_NOT_FOUND
454
        );
455
    }
456
    
457
   /**
458
    * Retrieves a placeholder instance by its replacement text.
459
    * 
460
    * @param string $string
461
    * @throws Mailcode_Exception
462
    * @return Mailcode_Parser_Safeguard_Placeholder
463
    */
464
    public function getPlaceholderByString(string $string) : Mailcode_Parser_Safeguard_Placeholder
465
    {
466
        $placeholders = $this->getPlaceholders();
467
        
468
        foreach($placeholders as $placeholder)
469
        {
470
            if($placeholder->getReplacementText() === $string)
471
            {
472
                return $placeholder;
473
            }
474
        }
475
        
476
        throw new Mailcode_Exception(
477
            'No such safeguard placeholder.',
478
            sprintf(
479
                'The placeholder replacement string [%s] is not present in the safeguard instance.',
480
                $string
481
            ),
482
            self::ERROR_PLACEHOLDER_NOT_FOUND
483
        );
484
    }
485
    
486
    public function hasPlaceholders() : bool
487
    {
488
        $placeholders = $this->getPlaceholders();
489
        
490
        return !empty($placeholders);
491
    }
492
}
493