Test Failed
Push — master ( 81711e...bf8774 )
by Sebastian
03:41
created

Mailcode_Parser_Safeguard::createFormatting()   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_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
    const ERROR_EMPTY_DELIMITER = 47803;
54
    const ERROR_PLACEHOLDER_NOT_FOUND = 47804;
55
    
56
   /**
57
    * @var Mailcode_Parser
58
    */
59
    protected $parser;
60
    
61
   /**
62
    * @var Mailcode_Collection
63
    */
64
    protected $commands;
65
    
66
   /**
67
    * @var string
68
    */
69
    protected $originalString;
70
    
71
   /**
72
    * @var Mailcode_Collection
73
    */
74
    protected $collection;
75
    
76
   /**
77
    * Counter for the placeholders, global for all placeholders.
78
    * @var integer
79
    */
80
    private static $counter = 0;
81
    
82
   /**
83
    * @var Mailcode_Parser_Safeguard_Placeholder[]
84
    */
85
    protected $placeholders;
86
    
87
   /**
88
    * @var string
89
    */
90
    protected $delimiter = '__';
91
    
92
   /**
93
    * @var string[]|NULL
94
    */
95
    protected $placeholderStrings;
96
    
97
   /**
98
    * @var Mailcode_Parser_Safeguard_Formatting|NULL
99
    */
100
    private $formatting = null;
0 ignored issues
show
introduced by
The private property $formatting is not used, and could be removed.
Loading history...
101
    
102
    public function __construct(Mailcode_Parser $parser, string $subject)
103
    {
104
        $this->parser = $parser;
105
        $this->originalString = $subject;
106
    }
107
    
108
   /**
109
    * Retrieves the string the safeguard was created for.
110
    * 
111
    * @return string
112
    */
113
    public function getOriginalString() : string
114
    {
115
        return $this->originalString;
116
    }
117
    
118
   /**
119
    * Sets the delimiter character sequence used to prepend
120
    * and append to the placeholders.
121
    * 
122
    * The delimiter's default is "__" (two underscores).
123
    * 
124
    * @param string $delimiter
125
    * @return Mailcode_Parser_Safeguard
126
    */
127
    public function setDelimiter(string $delimiter) : Mailcode_Parser_Safeguard
128
    {
129
        if(empty($delimiter))
130
        {
131
            throw new Mailcode_Exception(
132
                'Empty delimiter',
133
                'Delimiters may not be empty.',
134
                self::ERROR_EMPTY_DELIMITER
135
            );
136
        }
137
        
138
        $this->delimiter = $delimiter;
139
        
140
        return $this;
141
    }
142
    
143
    public function getDelimiter() : string
144
    {
145
        return $this->delimiter;
146
    }
147
    
148
   /**
149
    * Retrieves the safe string in which all commands have been replaced
150
    * by placeholder strings.
151
    *
152
    * @return string
153
    * @throws Mailcode_Exception 
154
    *
155
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
156
    */
157
    public function makeSafe() : string
158
    {
159
        $this->requireValidCollection();
160
        
161
        return $this->makeSafePartial();
162
    }
163
    
164
   /**
165
    * Like makeSafe(), but allows partial (invalid) commands: use this
166
    * if the subject string may contain only part of the whole set of
167
    * commands. 
168
    * 
169
    * Example: parsing a text with an opening if statement, without the 
170
    * matching end statement.
171
    * 
172
    * @return string
173
    */
174
    public function makeSafePartial() : string
175
    {
176
        $placeholders = $this->getPlaceholders();
177
        $string = $this->originalString;
178
        
179
        foreach($placeholders as $placeholder)
180
        {
181
            $pos = mb_strpos($string, $placeholder->getOriginalText());
182
            
183
            if($pos === false)
184
            {
185
                throw new Mailcode_Exception(
186
                    'Placeholder original text not found',
187
                    sprintf(
188
                        'Tried finding the command string [%s], but it has disappeared.',
189
                        $placeholder->getOriginalText()
190
                    ),
191
                    self::ERROR_PLACEHOLDER_NOT_FOUND
192
                );
193
            }
194
            
195
            $before = mb_substr($string, 0, $pos);
196
            $after = mb_substr($string, $pos + mb_strlen($placeholder->getOriginalText()));
197
            $string = $before.$placeholder->getReplacementText().$after;
198
        }
199
        
200
        return $string;
201
    }
202
    
203
   /**
204
    * Creates a formatting handler, which can be used to specify
205
    * which formattings to use for the commands in the subject string.
206
    * 
207
    * @param Mailcode_StringContainer|string $subject
208
    * @return Mailcode_Parser_Safeguard_Formatting
209
    */
210
    public function createFormatting($subject) : Mailcode_Parser_Safeguard_Formatting
211
    {
212
        if(is_string($subject))
213
        {
214
            $subject = Mailcode::create()->createString($subject);
215
        }
216
        
217
        return new Mailcode_Parser_Safeguard_Formatting($this, $subject);
218
    }
219
    
220
   /**
221
    * Retrieves all placeholders that have to be added to
222
    * the subject text.
223
    * 
224
    * @return \Mailcode\Mailcode_Parser_Safeguard_Placeholder[]
225
    */
226
    public function getPlaceholders()
227
    {
228
        if(isset($this->placeholders))
229
        {
230
            return $this->placeholders;
231
        }
232
        
233
        $this->placeholders = array();
234
        
235
        $cmds = $this->getCollection()->getCommands();
236
        
237
        foreach($cmds as $command)
238
        {
239
            self::$counter++;
240
            
241
            $this->placeholders[] = new Mailcode_Parser_Safeguard_Placeholder(
242
                self::$counter,
243
                $command,
244
                $this
245
            );
246
        }
247
248
        return $this->placeholders;
249
    }
250
    
251
    protected function restore(string $string, bool $partial=false, bool $highlighted=false) : string
252
    {
253
        if(!$partial)
254
        {
255
            $this->requireValidCollection();
256
        }
257
        
258
        $formatting = $this->createFormatting($string);
259
        
260
        if($highlighted)
261
        {
262
            $formatting->replaceWithHTMLHighlighting();
263
        }
264
        else 
265
        {
266
            $formatting->replaceWithNormalized();
267
        }
268
        
269
        return $formatting->toString();
270
    }
271
    
272
   /**
273
    * Makes the string whole again after transforming or filtering it,
274
    * by replacing the command placeholders with the original commands.
275
    *
276
    * @param string $string
277
    * @return string
278
    * @throws Mailcode_Exception
279
    *
280
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
281
    */
282
    public function makeWhole(string $string) : string
283
    {
284
        return $this->restore(
285
            $string, 
286
            false, // partial? 
287
            false // highlight?
288
        );
289
    }
290
    
291
   /**
292
    * Like `makeWhole()`, but ignores missing command placeholders.
293
    *
294
    * @param string $string
295
    * @return string
296
    * @throws Mailcode_Exception
297
    *
298
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
299
    */
300
    public function makeWholePartial(string $string) : string
301
    {
302
        return $this->restore(
303
            $string,
304
            true, // partial?
305
            false // highlight?
306
        );
307
    }
308
309
   /**
310
    * Like `makeWhole()`, but replaces the commands with a syntax
311
    * highlighted version, meant for human readable texts only.
312
    * 
313
    * Note: the commands lose their functionality (They cannot be 
314
    * parsed from that string again).
315
    *
316
    * @param string $string
317
    * @return string
318
    * @throws Mailcode_Exception
319
    *
320
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
321
    */
322
    public function makeHighlighted(string $string) : string
323
    {
324
        return $this->restore(
325
            $string, 
326
            false, // partial? 
327
            true // highlighted?
328
        );
329
    }
330
    
331
   /**
332
    * Like `makeHighlighted()`, but ignores missing command placeholders.
333
    * 
334
    * @param string $string
335
    * @return string
336
    * @throws Mailcode_Exception
337
    *
338
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
339
    */
340
    public function makeHighlightedPartial(string $string) : string
341
    {
342
        return $this->restore(
343
            $string, 
344
            true, // partial? 
345
            true // highlight?
346
        );
347
    }
348
    
349
   /**
350
    * Retrieves the commands collection contained in the string.
351
    * 
352
    * @return Mailcode_Collection
353
    */
354
    public function getCollection() : Mailcode_Collection
355
    {
356
        if(isset($this->collection))
357
        {
358
            return $this->collection;
359
        }
360
        
361
        $this->collection = $this->parser->parseString($this->originalString);
362
        
363
        return $this->collection;
364
    }
365
    
366
    public function isValid() : bool
367
    {
368
        return $this->getCollection()->isValid();
369
    }
370
    
371
   /**
372
    * @throws Mailcode_Exception
373
    * 
374
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
375
    */
376
    protected function requireValidCollection() : void
377
    {
378
        if($this->getCollection()->isValid())
379
        {
380
            return;
381
        }
382
        
383
        throw new Mailcode_Exception(
384
            'Cannot safeguard invalid commands',
385
            sprintf(
386
                'The collection contains invalid commands. Safeguarding is only allowed with valid commands.'.
387
                'Source string: [%s]',
388
                $this->originalString
389
            ),
390
            self::ERROR_INVALID_COMMANDS
391
        );
392
    }
393
    
394
   /**
395
    * Retrieves a list of all placeholder IDs used in the text.
396
    * 
397
    * @return string[]
398
    */
399
    public function getPlaceholderStrings() : array
400
    {
401
        if(is_array($this->placeholderStrings))
402
        {
403
            return $this->placeholderStrings;
404
        }
405
        
406
        $placeholders = $this->getPlaceholders();
407
        
408
        $this->placeholderStrings = array();
409
        
410
        foreach($placeholders as $placeholder)
411
        {
412
            $this->placeholderStrings[] = $placeholder->getReplacementText();
413
        }
414
        
415
        return $this->placeholderStrings;
416
    }
417
    
418
    public function isPlaceholder(string $subject) : bool
419
    {
420
        $ids = $this->getPlaceholderStrings();
421
        
422
        return in_array($subject, $ids);
423
    }
424
    
425
   /**
426
    * Retrieves a placeholder instance by its ID.
427
    * 
428
    * @param int $id
429
    * @throws Mailcode_Exception If the placeholder was not found.
430
    * @return Mailcode_Parser_Safeguard_Placeholder
431
    */
432
    public function getPlaceholderByID(int $id) : Mailcode_Parser_Safeguard_Placeholder
433
    {
434
        $placeholders = $this->getPlaceholders();
435
        
436
        foreach($placeholders as $placeholder)
437
        {
438
            if($placeholder->getID() === $id)
439
            {
440
                return $placeholder;
441
            }
442
        }
443
        
444
        throw new Mailcode_Exception(
445
            'No such safeguard placeholder.',
446
            sprintf(
447
                'The placeholder ID [%s] is not present in the safeguard instance.',
448
                $id
449
            ),
450
            self::ERROR_PLACEHOLDER_NOT_FOUND
451
        );
452
    }
453
    
454
   /**
455
    * Retrieves a placeholder instance by its replacement text.
456
    * 
457
    * @param string $string
458
    * @throws Mailcode_Exception
459
    * @return Mailcode_Parser_Safeguard_Placeholder
460
    */
461
    public function getPlaceholderByString(string $string) : Mailcode_Parser_Safeguard_Placeholder
462
    {
463
        $placeholders = $this->getPlaceholders();
464
        
465
        foreach($placeholders as $placeholder)
466
        {
467
            if($placeholder->getReplacementText() === $string)
468
            {
469
                return $placeholder;
470
            }
471
        }
472
        
473
        throw new Mailcode_Exception(
474
            'No such safeguard placeholder.',
475
            sprintf(
476
                'The placeholder replacement string [%s] is not present in the safeguard instance.',
477
                $string
478
            ),
479
            self::ERROR_PLACEHOLDER_NOT_FOUND
480
        );
481
    }
482
    
483
    public function hasPlaceholders() : bool
484
    {
485
        $placeholders = $this->getPlaceholders();
486
        
487
        return !empty($placeholders);
488
    }
489
}
490