Passed
Push — master ( d31021...33110a )
by Sebastian
64:00
created

Mailcode_Parser_Safeguard::getOriginalString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
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
   /**
61
    * @var Mailcode_Parser
62
    */
63
    protected $parser;
64
    
65
   /**
66
    * @var Mailcode_Collection
67
    */
68
    protected $commands;
69
    
70
   /**
71
    * @var string
72
    */
73
    protected $originalString;
74
    
75
   /**
76
    * @var Mailcode_Collection
77
    */
78
    protected $collection;
79
    
80
   /**
81
    * Counter for the placeholders, global for all placeholders.
82
    * @var integer
83
    */
84
    private static $counter = 0;
85
    
86
   /**
87
    * @var Mailcode_Parser_Safeguard_Placeholder[]
88
    */
89
    protected $placeholders;
90
    
91
   /**
92
    * @var string
93
    */
94
    protected $delimiter = '__';
95
    
96
   /**
97
    * @var string[]|NULL
98
    */
99
    protected $placeholderStrings;
100
    
101
    public function __construct(Mailcode_Parser $parser, string $subject)
102
    {
103
        $this->parser = $parser;
104
        $this->originalString = $subject;
105
    }
106
    
107
   /**
108
    * Retrieves the string the safeguard was created for.
109
    * 
110
    * @return string
111
    */
112
    public function getOriginalString() : string
113
    {
114
        return $this->originalString;
115
    }
116
    
117
   /**
118
    * Sets the delimiter character sequence used to prepend
119
    * and append to the placeholders.
120
    * 
121
    * The delimiter's default is "__" (two underscores).
122
    * 
123
    * @param string $delimiter
124
    * @return Mailcode_Parser_Safeguard
125
    */
126
    public function setDelimiter(string $delimiter) : Mailcode_Parser_Safeguard
127
    {
128
        if(empty($delimiter))
129
        {
130
            throw new Mailcode_Exception(
131
                'Empty delimiter',
132
                'Delimiters may not be empty.',
133
                self::ERROR_EMPTY_DELIMITER
134
            );
135
        }
136
        
137
        $this->delimiter = $delimiter;
138
        
139
        return $this;
140
    }
141
    
142
    public function getDelimiter() : string
143
    {
144
        return $this->delimiter;
145
    }
146
    
147
   /**
148
    * Retrieves the safe string in which all commands have been replaced
149
    * by placeholder strings.
150
    * 
151
    * @return string
152
    * @throws Mailcode_Exception 
153
    *
154
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
155
    */
156
    public function makeSafe() : string
157
    {
158
        $this->requireValidCollection();
159
        
160
        $replaces = $this->getReplaces();
161
                
162
        return str_replace(array_values($replaces), array_keys($replaces), $this->originalString);
163
    }
164
    
165
   /**
166
    * @param bool $highlighted
167
    * @return string[]string
168
    */
169
    protected function getReplaces(bool $highlighted=false) : array
170
    {
171
        $placeholders = $this->getPlaceholders();
172
        
173
        $replaces = array();
174
        
175
        foreach($placeholders as $placeholder)
176
        {
177
            $replace = '';
178
            
179
            if($highlighted)
180
            {
181
                $replace = $placeholder->getHighlightedText();
182
            }
183
            else 
184
            {
185
                $replace = $placeholder->getOriginalText();
186
            }
187
            
188
            $replaces[$placeholder->getReplacementText()] = $replace;
189
        }
190
        
191
        return $replaces;
192
    }
193
    
194
    
195
   /**
196
    * Retrieves all placeholders that have to be added to
197
    * the subject text.
198
    * 
199
    * @return \Mailcode\Mailcode_Parser_Safeguard_Placeholder[]
200
    */
201
    public function getPlaceholders()
202
    {
203
        if(isset($this->placeholders))
204
        {
205
            return $this->placeholders;
206
        }
207
        
208
        $this->placeholders = array();
209
        
210
        $cmds = $this->getCollection()->getGroupedByHash();
211
        
212
        foreach($cmds as $command)
213
        {
214
            self::$counter++;
215
            
216
            $this->placeholders[] = new Mailcode_Parser_Safeguard_Placeholder(
217
                self::$counter,
218
                $command,
219
                $this
220
            );
221
        }
222
223
        return $this->placeholders;
224
    }
225
    
226
    protected function restore(string $string, bool $highlighted=false) : string
227
    {
228
        $this->requireValidCollection();
229
        
230
        $replaces = $this->getReplaces($highlighted);
231
        
232
        $placeholderStrings = array_keys($replaces);
233
        
234
        foreach($placeholderStrings as $search)
235
        {
236
            if(!strstr($string, $search))
237
            {
238
                throw new Mailcode_Exception(
239
                    'Command placeholder not found',
240
                    sprintf(
241
                        'A placeholder for a command could not be found in the string to restore: [%s].',
242
                        $search
243
                    ),
244
                    self::ERROR_COMMAND_PLACEHOLDER_MISSING
245
                );
246
            }
247
        }
248
        
249
        return str_replace($placeholderStrings, array_values($replaces), $string);
250
    }
251
    
252
   /**
253
    * Makes the string whole again after transforming or filtering it,
254
    * by replacing the command placeholders with the original commands.
255
    *
256
    * @param string $string
257
    * @return string
258
    * @throws Mailcode_Exception
259
    *
260
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
261
    * @see Mailcode_Parser_Safeguard::ERROR_COMMAND_PLACEHOLDER_MISSING
262
    */
263
    public function makeWhole(string $string) : string
264
    {
265
        return $this->restore($string, false);
266
    }
267
268
   /**
269
    * Like makeWhole(), but replaces the commands with a syntax
270
    * highlighted version, meant for human readable texts only.
271
    * 
272
    * Note: the commands lose their functionality (They cannot be 
273
    * parsed from that string again).
274
    *
275
    * @param string $string
276
    * @return string
277
    * @throws Mailcode_Exception
278
    *
279
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
280
    * @see Mailcode_Parser_Safeguard::ERROR_COMMAND_PLACEHOLDER_MISSING
281
    */
282
    public function makeHighlighted(string $string) : string
283
    {
284
        return $this->restore($string, true);
285
    }
286
    
287
   /**
288
    * Retrieves the commands collection contained in the string.
289
    * 
290
    * @return Mailcode_Collection
291
    */
292
    public function getCollection() : Mailcode_Collection
293
    {
294
        if(isset($this->collection))
295
        {
296
            return $this->collection;
297
        }
298
        
299
        $this->collection = $this->parser->parseString($this->originalString);
300
        
301
        return $this->collection;
302
    }
303
    
304
    public function isValid() : bool
305
    {
306
        return $this->getCollection()->isValid();
307
    }
308
    
309
   /**
310
    * @throws Mailcode_Exception
311
    * 
312
    * @see Mailcode_Parser_Safeguard::ERROR_INVALID_COMMANDS
313
    */
314
    protected function requireValidCollection() : void
315
    {
316
        if($this->getCollection()->isValid())
317
        {
318
            return;
319
        }
320
        
321
        throw new Mailcode_Exception(
322
            'Cannot safeguard invalid commands',
323
            sprintf(
324
                'The collection contains invalid commands. Safeguarding is only allowed with valid commands.'.
325
                'Source string: [%s]',
326
                $this->originalString
327
            ),
328
            self::ERROR_INVALID_COMMANDS
329
        );
330
    }
331
    
332
   /**
333
    * Retrieves a list of all placeholder IDs used in the text.
334
    * 
335
    * @return string[]
336
    */
337
    public function getPlaceholderStrings() : array
338
    {
339
        if(isset($this->placeholderStrings))
340
        {
341
            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...
342
        }
343
        
344
        $placeholders = $this->getPlaceholders();
345
        
346
        $this->placeholderStrings = array();
347
        
348
        foreach($placeholders as $placeholder)
349
        {
350
            $this->placeholderStrings[] = $placeholder->getReplacementText();
351
        }
352
        
353
        return $this->placeholderStrings;
354
    }
355
    
356
    public function isPlaceholder(string $subject) : bool
357
    {
358
        $ids = $this->getPlaceholderStrings();
359
        
360
        return in_array($subject, $ids);
361
    }
362
    
363
   /**
364
    * Retrieves a placeholder instance by its ID.
365
    * 
366
    * @param int $id
367
    * @throws Mailcode_Exception If the placeholder was not found.
368
    * @return Mailcode_Parser_Safeguard_Placeholder
369
    */
370
    public function getPlaceholderByID(int $id) : Mailcode_Parser_Safeguard_Placeholder
371
    {
372
        $placeholders = $this->getPlaceholders();
373
        
374
        foreach($placeholders as $placeholder)
375
        {
376
            if($placeholder->getID() === $id)
377
            {
378
                return $placeholder;
379
            }
380
        }
381
        
382
        throw new Mailcode_Exception(
383
            'No such safeguard placeholder.',
384
            sprintf(
385
                'The placeholder ID [%s] is not present in the safeguard instance.',
386
                $id
387
            ),
388
            self::ERROR_PLACEHOLDER_NOT_FOUND
389
        );
390
    }
391
    
392
   /**
393
    * Retrieves a placeholder instance by its replacement text.
394
    * 
395
    * @param string $string
396
    * @throws Mailcode_Exception
397
    * @return Mailcode_Parser_Safeguard_Placeholder
398
    */
399
    public function getPlaceholderByString(string $string) : Mailcode_Parser_Safeguard_Placeholder
400
    {
401
        $placeholders = $this->getPlaceholders();
402
        
403
        foreach($placeholders as $placeholder)
404
        {
405
            if($placeholder->getReplacementText() === $string)
406
            {
407
                return $placeholder;
408
            }
409
        }
410
        
411
        throw new Mailcode_Exception(
412
            'No such safeguard placeholder.',
413
            sprintf(
414
                'The placeholder replacement string [%s] is not present in the safeguard instance.',
415
                $string
416
            ),
417
            self::ERROR_PLACEHOLDER_NOT_FOUND
418
        );
419
    }
420
    
421
    public function hasPlaceholders() : bool
422
    {
423
        $placeholders = $this->getPlaceholders();
424
        
425
        return !empty($placeholders);
426
    }
427
}
428