Completed
Pull Request — 5.0 (#1)
by Huberty
03:14
created

SwiftTwigMailTemplate::keepTag()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 6
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 4
crap 6
1
<?php
2
3
namespace TheCodingMachine\Mail\Template;
4
5
use TheCodingMachine\Mail\SwiftMailTemplate;
6
7
class SwiftTwigMailTemplate implements SwiftMailTemplate
8
{
9
    /**
10
     * @var \Twig_Environment
11
     */
12
    protected $twigEnvironment;
13
14
    /**
15
     * @var string
16
     */
17
    protected $twigPath;
18
19
    /**
20
     * @var string|array
21
     */
22
    protected $fromAddresses;
23
24
    /**
25
     * @var string
26
     */
27
    protected $fromName = null;
28
29
    /**
30
     * @var string|array
31
     */
32
    protected $toAddresses;
33
34
    /**
35
     * @var string
36
     */
37
    protected $toName = null;
38
39
    /**
40
     * @var string|array
41
     */
42
    protected $bccAddresses;
43
44
    /**
45
     * @var string
46
     */
47
    protected $bccName = null;
48
49
    /**
50
     * @var string|array
51
     */
52
    protected $ccAddresses;
53
54
    /**
55
     * @var string
56
     */
57
    protected $ccName = null;
58
59
    /**
60
     * @var string|array
61
     */
62
    protected $replyToAddresses;
63
64
    /**
65
     * @var string
66
     */
67
    protected $replyToName = null;
68
69
    /**
70
     * @var int
71
     */
72
    protected $maxLineLength = 1000;
73
74
    /**
75
     * @var int
76
     */
77
    protected $priority;
78
79
    /**
80
     * @var string
81
     */
82
    protected $readReceiptTo;
83
84
    /**
85
     * @var string
86
     */
87
    protected $returnPath;
88
89
    /**
90
     * SwiftTwigMailGenerator constructor.
91
     *
92
     * @param \Twig_Environment $twig_Environment
93
     * @param string            $twigPath
94
     */
95 2
    public function __construct(\Twig_Environment $twig_Environment, string $twigPath)
96
    {
97 2
        $this->twigEnvironment = $twig_Environment;
98 2
        $this->twigPath = $twigPath;
99 2
    }
100
101
    /**
102
     * @param array $data
103
     *
104
     * @return \Swift_Message
105
     */
106 2
    public function renderMail(array $data = []) :\Swift_Message
107
    {
108 2
        $mail = new \Swift_Message();
109
110 2
        $twigEnvironment = clone $this->twigEnvironment;
111 2
        $function = new \Twig_SimpleFunction('embedImage', function ($imgPath) use ($mail) {
112
            return $mail->embed(\Swift_Image::fromPath($imgPath));
113 2
        });
114 2
        $twigEnvironment->addFunction($function);
115
116 2
        $template = $twigEnvironment->loadTemplate($this->twigPath);
117
118 2
        if (!$template->hasBlock('subject') || !$template->hasBlock('body_html')) {
119 1
            throw MissingBlockException::missingBlock($template->getBlockNames());
120
        }
121
122 1
        $subject = $template->renderBlock('subject', $data);
0 ignored issues
show
Bug introduced by
The method renderBlock() does not exist on Twig_TemplateInterface. Did you maybe mean render()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
123 1
        $bodyHtml = $template->renderBlock('body_html', $data);
0 ignored issues
show
Bug introduced by
The method renderBlock() does not exist on Twig_TemplateInterface. Did you maybe mean render()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
124 1
        if (!$template->hasBlock('body_text')) {
125
            $bodyText = $this->removeHtml($bodyHtml);
126
        } else {
127 1
            $bodyText = $template->renderBlock('body_text', $data);
0 ignored issues
show
Bug introduced by
The method renderBlock() does not exist on Twig_TemplateInterface. Did you maybe mean render()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
128
        }
129
130 1
        $mail->setSubject($subject);
131 1
        $mail->setBody($bodyHtml);
132 1
        $mail->addPart($bodyText);
133
134
        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $this->maxLineLength of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $this->priority of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $this->readReceiptTo of type string to the boolean true. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $this->returnPath of type string to the boolean true. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
135 1
            case $this->fromAddresses:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
136 1
                $mail->setFrom($this->fromAddresses, $this->fromName);
137 1
                $mail->setSender($this->fromAddresses, $this->fromName);
1 ignored issue
show
Bug introduced by
It seems like $this->fromAddresses can also be of type array; however, Swift_Mime_SimpleMessage::setSender() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
138
            case $this->toAddresses:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
139 1
                $mail->setTo($this->toAddresses, $this->toName);
140
            case $this->bccAddresses:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
141 1
                $mail->setBcc($this->bccAddresses, $this->bccName);
142
            case $this->ccAddresses:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
143 1
                $mail->setCc($this->ccAddresses, $this->ccName);
144
            case $this->replyToAddresses:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
145 1
                $mail->setReplyTo($this->replyToAddresses, $this->replyToName);
146
            case $this->maxLineLength:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
147 1
                $mail->setMaxLineLength($this->maxLineLength);
148
            case $this->priority:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
149 1
                $mail->setPriority($this->priority);
150
            case $this->readReceiptTo:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
151 1
                $mail->setReadReceiptTo($this->readReceiptTo);
0 ignored issues
show
Documentation introduced by
$this->readReceiptTo is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
152
            case $this->returnPath:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
153 1
                $mail->setReturnPath($this->returnPath);
154
            default:
155 1
                break;
156
        }
157
158 1
        return $mail;
159
    }
160
161
    /**
162
     * @param array|string $fromAddresses
163
     */
164 1
    public function setFromAddresses($fromAddresses)
165
    {
166 1
        $this->fromAddresses = $fromAddresses;
167 1
    }
168
169
    /**
170
     * @param string $fromName
171
     */
172 1
    public function setFromName($fromName)
173
    {
174 1
        $this->fromName = $fromName;
175 1
    }
176
177
    /**
178
     * @param array|string $toAddresses
179
     */
180 1
    public function setToAddresses($toAddresses)
181
    {
182 1
        $this->toAddresses = $toAddresses;
183 1
    }
184
185
    /**
186
     * @param string $toName
187
     */
188 1
    public function setToName($toName)
189
    {
190 1
        $this->toName = $toName;
191 1
    }
192
193
    /**
194
     * @param array|string $bccAddresses
195
     */
196 1
    public function setBccAddresses($bccAddresses)
197
    {
198 1
        $this->bccAddresses = $bccAddresses;
199 1
    }
200
201
    /**
202
     * @param string $bccName
203
     */
204 1
    public function setBccName($bccName)
205
    {
206 1
        $this->bccName = $bccName;
207 1
    }
208
209
    /**
210
     * @param array|string $ccAddresses
211
     */
212 1
    public function setCcAddresses($ccAddresses)
213
    {
214 1
        $this->ccAddresses = $ccAddresses;
215 1
    }
216
217
    /**
218
     * @param string $ccName
219
     */
220 1
    public function setCcName($ccName)
221
    {
222 1
        $this->ccName = $ccName;
223 1
    }
224
225
    /**
226
     * @param array|string $replyToAddresses
227
     */
228 1
    public function setReplyToAddresses($replyToAddresses)
229
    {
230 1
        $this->replyToAddresses = $replyToAddresses;
231 1
    }
232
233
    /**
234
     * @param string $replyToName
235
     */
236 1
    public function setReplyToName($replyToName)
237
    {
238 1
        $this->replyToName = $replyToName;
239 1
    }
240
241
    /**
242
     * @param int $maxLineLength
243
     */
244 1
    public function setMaxLineLength($maxLineLength)
245
    {
246 1
        $this->maxLineLength = $maxLineLength;
247 1
    }
248
249
    /**
250
     * @param int $priority
251
     */
252 1
    public function setPriority($priority)
253
    {
254 1
        $this->priority = $priority;
255 1
    }
256
257
    /**
258
     * @param string $readReceiptTo
259
     */
260 1
    public function setReadReceiptTo($readReceiptTo)
261
    {
262 1
        $this->readReceiptTo = $readReceiptTo;
263 1
    }
264
265
    /**
266
     * @param string $returnPath
267
     */
268 1
    public function setReturnPath($returnPath)
269
    {
270 1
        $this->returnPath = $returnPath;
271 1
    }
272
273
    /**
274
     * Removes the HTML tags from the text.
275
     *
276
     * @param string $s
277
     * @param string $keep   The list of tags to keep
278
     * @param string $expand The list of tags to remove completely, along their content
279
     */
280
    private function removeHtml(string $s, string $keep = '', string $expand = 'script|style|noframes|select|option') :string
281
    {
282
        /**///prep the string
283
        $s = ' '.$s;
284
285
        /**///initialize keep tag logic
286
        if (strlen($keep) > 0) {
287
            $k = explode('|', $keep);
288
            $s = $this->keepTag($s, $k, '<', '[{(');
289
        }
290
        $pos = array();
291
        $len = array();
292
293
        //begin removal
294
        /**///remove comment blocks
295
        $s = $this->removeElement($s, '<!--', '-->');
296
297
        /**///remove tags with content between them
298
        if (strlen($expand) > 0) {
299
            $e = explode('|', $expand);
300
            for ($i = 0;$i < count($e);++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
301
                while (stripos($s, '<'.$e[$i]) > 0) {
302
                    $len[1] = strlen('<'.$e[$i]);
303
                    $pos[1] = stripos($s, '<'.$e[$i]);
304
                    $pos[2] = stripos($s, $e[$i].'>', $pos[1] + $len[1]);
305
                    $len[2] = $pos[2] - $pos[1] + $len[1];
306
                    $x = substr($s, $pos[1], $len[2]);
307
                    $s = str_replace($x, '', $s);
308
                }
309
            }
310
        }
311
312
        /**///remove remaining tags
313
        $s = $this->removeElement($s, '<', '>');
314
315
        /**///finalize keep tag
316
        if (isset($k)) {
317
            $s = $this->keepTag($s, $k, '[{(', '<');
318
        }
319
320
        return trim($s);
321
    }
322
323
    /**
324
     * @param string $s
325
     * @param string $openTag
326
     * @param string $closeTag
327
     *
328
     * @return mixed|string
329
     */
330
    private function removeElement(string $s, string $openTag, string $closeTag)
331
    {
332
        while (stripos($s, $openTag) > 0) {
333
            $pos[1] = stripos($s, $openTag);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$pos was never initialized. Although not strictly required by PHP, it is generally a good practice to add $pos = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
334
            $pos[2] = stripos($s, $closeTag, $pos[1]);
0 ignored issues
show
Bug introduced by
The variable $pos does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
335
            $len[1] = $pos[2] - $pos[1] + 1;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$len was never initialized. Although not strictly required by PHP, it is generally a good practice to add $len = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
336
            $x = substr($s, $pos[1], $len[1]);
0 ignored issues
show
Bug introduced by
The variable $len does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
337
            $s = str_replace($x, '', $s);
338
        }
339
340
        return $s;
341
    }
342
343
    /**
344
     * @param string $s
345
     * @param array  $tagToKeep
346
     * @param string $initial
347
     * @param string $finalize
348
     *
349
     * @return string
350
     */
351
    private function keepTag(string $s, array $tagToKeep, string $initial, string $finalize):string
352
    {
353
        $count = count($tagToKeep);
354
        for ($i = 0;$i < $count;++$i) {
355
            $s = str_replace($initial.$tagToKeep[$i], $finalize.$tagToKeep[$i], $s);
356
            $s = str_replace($initial.'/'.$tagToKeep[$i], $finalize.'/'.$tagToKeep[$i], $s);
357
        }
358
359
        return $s;
360
    }
361
}
362