ChimpDrill::parseMessage()   B
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
cc 3
eloc 11
nc 2
nop 0
1
<?php
2
3
namespace FlorianKoerner\ChimpDrill;
4
5
/**
6
 * ChimpDrill a simple mailchimp / mandrill merge tags parser
7
 */
8
class ChimpDrill
9
{
10
    /**
11
     * @var array callback => syntax pattern
12
     */
13
    protected $pattern = array(
14
        'placeholder' => '/\*\|([A-Za-z0-9_]+)\|\*/',
15
        'if'          => '/\*\|(IF|IFNOT|ELSEIF):([A-Za-z0-9_]+)(?:[\s]*(=|!=|&gt;=|&lt;=|&gt;|&lt;)[\s]*(.+?))?\|\*/',
16
        'else'        => '/\*\|ELSE:\|\*/',
17
        'endif'       => '/\*\|END:IF\|\*/',
18
        'filter'      => '/\*\|(HTML|TITLE|LOWER|UPPER):([A-Za-z0-9_]+)\|\*/',
19
        'date'        => '/\*\|DATE:(.+?)\|\*/'
20
    );
21
22
    /**
23
     * @var string parsed message
24
     */
25
    protected $parsed = null;
26
27
    /**
28
     * @var string message
29
     */
30
    protected $message;
31
32
    /**
33
     * @var array placeholder
34
     */
35
    protected $placeholder;
36
37
    /**
38
     * @param string $message     Message to parse
39
     * @param array  $placeholder Placeholder
40
     */
41
    public function __construct($message, array $placeholder)
42
    {
43
        $this->message = $message;
44
        $this->placeholder = $placeholder;
45
    }
46
47
    /**
48
     * @return string
49
     */
50
    public function __toString()
51
    {
52
        return $this->getParsed();
53
    }
54
55
    /**
56
     * Returns parsed message.
57
     * 
58
     * @return string
59
     */
60
    public function getParsed()
61
    {
62
        $this->parseMessage();
63
64
        return $this->parsed;
65
    }
66
67
    /**
68
     * Parse the message (If this haven't be done yet).
69
     * 
70
     * @return ChimpDrill
71
     */
72
    protected function parseMessage()
73
    {
74
        if ($this->parsed === null) {
75
            // Escape message
76
            $message = $this->escapeValue($this->message);
77
78
            // Replace Syntax with PHP
79
            foreach ($this->pattern as $type => $pattern) {
80
                $method = 'parse' . ucfirst($type);
81
                $message = preg_replace_callback($pattern, array($this, $method), $message);
82
            }
83
84
            // Write file
85
            $file = tempnam('/tmp', 'chimpdrill-');
86
87
            file_put_contents($file, '<?php ob_start(); ?>' . $message . '<?php return ob_get_clean(); ?>');
88
89
            $this->parsed = $this->unescapeValue(include_once($file));
90
91
            unlink($file);
92
        }
93
94
        return $this;
95
    }
96
97
    /**
98
     * Searches for a placeholder and returns the found or default value.
99
     * 
100
     * @param string $name
101
     * @param mixed  $default
102
     * 
103
     * @return mixed
104
     */
105
    protected function getPlaceholder($name, $default = null)
106
    {
107
        return isset($this->placeholder[$name]) ? $this->placeholder[$name] : $default;
108
    }
109
110
    /**
111
     * @param mixed $value
112
     * 
113
     * @return mixed
114
     */
115
    protected function exportValue($value)
116
    {
117
        return var_export($value, true);
118
    }
119
120
    /**
121
     * Escape an string.
122
     * 
123
     * @param string $value
124
     * 
125
     * @return string
126
     */
127
    protected function escapeValue($value)
128
    {
129
        return htmlspecialchars($value, null, 'UTF-8');
130
    }
131
132
    /**
133
     * Rolls back escaping.
134
     * 
135
     * @param string $value
136
     * 
137
     * @return string
138
     */
139
    protected function unescapeValue($value)
140
    {
141
        return htmlspecialchars_decode($value);
142
    }
143
144
    /**
145
     * Compares two values with an operator.
146
     * 
147
     * @param mixed  $val1
148
     * @param string $operator
149
     * @param mixed  $val2
150
     * 
151
     * @return boolean
152
     */
153
    protected function compare($val1, $operator, $val2)
154
    {
155
        switch ($operator) {
156
            case '=':
157
                return ($val1 == $val2);
158
159
            case '!=':
160
                return ($val1 != $val2);
161
162
            case '>=':
163
                return ($val1 >= $val2);
164
165
            case '<=':
166
                return ($val1 <= $val2);
167
168
            case '>':
169
                return ($val1 > $val2);
170
171
            case '<':
172
                return ($val1 < $val2);
173
        }
174
    }
175
176
    /**
177
     * Parses placeholder merge tags.
178
     * 
179
     * @param array $match
180
     * 
181
     * @return string
182
     */
183
    protected function parsePlaceholder(array $match)
184
    {
185
        // Yes, double escaping is correct here
186
        return $this->escapeValue(
187
            $this->escapeValue(
188
                $this->getPlaceholder($match[1], '*|' . $match[1] . '|*')
189
            )
190
        );
191
    }
192
193
    /**
194
     * Parses `IF|ELSEIF|IFNOT` conditional merge tags.
195
     *
196
     * @param array $match
197
     * 
198
     * @return string
199
     */
200
    protected function parseIf(array $match)
201
    {
202
        $condition = $this->getPlaceholder($match[2]);
203
204
        if (count($match) == 5) {
205
            $condition = $this->compare($condition, $this->unescapeValue($match[3]), $this->getPlaceholder($match[4], $match[4]));
206
        } else {
207
            $condition = (bool) $condition;
208
        }
209
210
        switch ($match[1]) {
211
            case 'IF':
212
                return '<?php if (' . $this->exportValue($condition) . '): ?>';
213
214
            case 'ELSEIF':
215
                return '<?php elseif (' . $this->exportValue($condition) . '): ?>';
216
217
            case 'IFNOT':
218
                return '<?php if (!' . $this->exportValue($condition) . '): ?>';
219
        }
220
    }
221
222
    /**
223
     * Parses `ELSE` conditional merge tags.
224
     * 
225
     * @return string
226
     */
227
    protected function parseElse()
228
    {
229
        return '<?php else: ?>';
230
    }
231
232
    /**
233
     * Parses `ENDIF` conditional merge tags.
234
     * 
235
     * @return string
236
     */
237
    protected function parseEndif()
238
    {
239
        return '<?php endif; ?>';
240
    }
241
242
    /**
243
     * Parses `HTML|TITLE|LOWER|UPPER` filter merge tags.
244
     *
245
     * @param array $match
246
     *
247
     * @return string
248
     */
249
    protected function parseFilter(array $match)
250
    {
251
        $value = $this->getPlaceholder($match[2], '*|' . $match[2] . '|*');
252
253
        switch ($match[1]) {
254
            case 'HTML':
255
                return $this->escapeValue($value);
256
257
            case 'TITLE':
258
                return ucwords(strtolower($value));
259
260
            case 'LOWER':
261
                return strtolower($value);
262
263
            case 'UPPER':
264
                return strtoupper($value);
265
        }
266
    }
267
268
    /**
269
     * Parses date merge tags.
270
     *
271
     * @param array $match
272
     *
273
     * @return bool|string
274
     */
275
    protected function parseDate(array $match)
276
    {
277
        return date($match[1] ?: 'Y-m-d');
278
    }
279
}
280