Passed
Push — master ( 4d3781...3e400f )
by Sebastian
04:07
created

makeHighlightedPartial()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
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 $partial=false, 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(!$partial && !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(
331
            $string, 
332
            false, // partial? 
333
            false // highlight?
334
        );
335
    }
336
    
337
   /**
338
    * Like `makeWhole()`, but ignores missing command placeholders.
339
    *
340
    * @param string $string
341
    * @return string
342
    * @throws Mailcode_Exception
343
    *
344
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
345
    */
346
    public function makeWholePartial(string $string) : string
347
    {
348
        return $this->restore(
349
            $string,
350
            true, // partial?
351
            false // highlight?
352
        );
353
    }
354
355
   /**
356
    * Like `makeWhole()`, but replaces the commands with a syntax
357
    * highlighted version, meant for human readable texts only.
358
    * 
359
    * Note: the commands lose their functionality (They cannot be 
360
    * parsed from that string again).
361
    *
362
    * @param string $string
363
    * @return string
364
    * @throws Mailcode_Exception
365
    *
366
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
367
    * @see Mailcode_Parser_Safeguard::ERROR_COMMAND_PLACEHOLDER_MISSING
368
    */
369
    public function makeHighlighted(string $string) : string
370
    {
371
        return $this->restore(
372
            $string, 
373
            false, // partial? 
374
            true // highlighted?
375
        );
376
    }
377
    
378
   /**
379
    * Like `makeHighlighted()`, but ignores missing command placeholders.
380
    * 
381
    * @param string $string
382
    * @return string
383
    * @throws Mailcode_Exception
384
    *
385
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
386
    */
387
    public function makeHighlightedPartial(string $string) : string
388
    {
389
        return $this->restore(
390
            $string, 
391
            true, // partial? 
392
            true // highlight?
393
        );
394
    }
395
    
396
   /**
397
    * Retrieves the commands collection contained in the string.
398
    * 
399
    * @return Mailcode_Collection
400
    */
401
    public function getCollection() : Mailcode_Collection
402
    {
403
        if(isset($this->collection))
404
        {
405
            return $this->collection;
406
        }
407
        
408
        $this->collection = $this->parser->parseString($this->originalString);
409
        
410
        return $this->collection;
411
    }
412
    
413
    public function isValid() : bool
414
    {
415
        return $this->getCollection()->isValid();
416
    }
417
    
418
   /**
419
    * @throws Mailcode_Exception
420
    * 
421
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
422
    */
423
    protected function requireValidCollection() : void
424
    {
425
        if($this->getCollection()->isValid())
426
        {
427
            return;
428
        }
429
        
430
        throw new Mailcode_Exception(
431
            'Cannot safeguard invalid commands',
432
            sprintf(
433
                'The collection contains invalid commands. Safeguarding is only allowed with valid commands.'.
434
                'Source string: [%s]',
435
                $this->originalString
436
            ),
437
            self::ERROR_INVALID_COMMANDS
438
        );
439
    }
440
    
441
   /**
442
    * Retrieves a list of all placeholder IDs used in the text.
443
    * 
444
    * @return string[]
445
    */
446
    public function getPlaceholderStrings() : array
447
    {
448
        if(is_array($this->placeholderStrings))
449
        {
450
            return $this->placeholderStrings;
451
        }
452
        
453
        $placeholders = $this->getPlaceholders();
454
        
455
        $this->placeholderStrings = array();
456
        
457
        foreach($placeholders as $placeholder)
458
        {
459
            $this->placeholderStrings[] = $placeholder->getReplacementText();
460
        }
461
        
462
        return $this->placeholderStrings;
463
    }
464
    
465
    public function isPlaceholder(string $subject) : bool
466
    {
467
        $ids = $this->getPlaceholderStrings();
468
        
469
        return in_array($subject, $ids);
470
    }
471
    
472
   /**
473
    * Retrieves a placeholder instance by its ID.
474
    * 
475
    * @param int $id
476
    * @throws Mailcode_Exception If the placeholder was not found.
477
    * @return Mailcode_Parser_Safeguard_Placeholder
478
    */
479
    public function getPlaceholderByID(int $id) : Mailcode_Parser_Safeguard_Placeholder
480
    {
481
        $placeholders = $this->getPlaceholders();
482
        
483
        foreach($placeholders as $placeholder)
484
        {
485
            if($placeholder->getID() === $id)
486
            {
487
                return $placeholder;
488
            }
489
        }
490
        
491
        throw new Mailcode_Exception(
492
            'No such safeguard placeholder.',
493
            sprintf(
494
                'The placeholder ID [%s] is not present in the safeguard instance.',
495
                $id
496
            ),
497
            self::ERROR_PLACEHOLDER_NOT_FOUND
498
        );
499
    }
500
    
501
   /**
502
    * Retrieves a placeholder instance by its replacement text.
503
    * 
504
    * @param string $string
505
    * @throws Mailcode_Exception
506
    * @return Mailcode_Parser_Safeguard_Placeholder
507
    */
508
    public function getPlaceholderByString(string $string) : Mailcode_Parser_Safeguard_Placeholder
509
    {
510
        $placeholders = $this->getPlaceholders();
511
        
512
        foreach($placeholders as $placeholder)
513
        {
514
            if($placeholder->getReplacementText() === $string)
515
            {
516
                return $placeholder;
517
            }
518
        }
519
        
520
        throw new Mailcode_Exception(
521
            'No such safeguard placeholder.',
522
            sprintf(
523
                'The placeholder replacement string [%s] is not present in the safeguard instance.',
524
                $string
525
            ),
526
            self::ERROR_PLACEHOLDER_NOT_FOUND
527
        );
528
    }
529
    
530
    public function hasPlaceholders() : bool
531
    {
532
        $placeholders = $this->getPlaceholders();
533
        
534
        return !empty($placeholders);
535
    }
536
}
537