Passed
Push — master ( 89d9c2...d06d8f )
by Sebastian
02:25
created

Mailcode_Parser_Safeguard::hasPlaceholders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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