1<?php
2
3/*
4 * This file is part of SwiftMailer.
5 * (c) 2004-2009 Chris Corbyn
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11/**
12 * Allows customization of Messages on-the-fly.
13 *
14 * @author Chris Corbyn
15 * @author Fabien Potencier
16 */
17class Swift_Plugins_DecoratorPlugin implements Swift_Events_SendListener, Swift_Plugins_Decorator_Replacements
18{
19    /** The replacement map */
20    private $replacements;
21
22    /** The body as it was before replacements */
23    private $originalBody;
24
25    /** The original headers of the message, before replacements */
26    private $originalHeaders = array();
27
28    /** Bodies of children before they are replaced */
29    private $originalChildBodies = array();
30
31    /** The Message that was last replaced */
32    private $lastMessage;
33
34    /**
35     * Create a new DecoratorPlugin with $replacements.
36     *
37     * The $replacements can either be an associative array, or an implementation
38     * of {@link Swift_Plugins_Decorator_Replacements}.
39     *
40     * When using an array, it should be of the form:
41     * <code>
42     * $replacements = array(
43     *  "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"),
44     *  "address2@domain.tld" => array("{a}" => "x", "{c}" => "y")
45     * )
46     * </code>
47     *
48     * When using an instance of {@link Swift_Plugins_Decorator_Replacements},
49     * the object should return just the array of replacements for the address
50     * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}.
51     *
52     * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
53     */
54    public function __construct($replacements)
55    {
56        $this->setReplacements($replacements);
57    }
58
59    /**
60     * Sets replacements.
61     *
62     * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
63     *
64     * @see __construct()
65     */
66    public function setReplacements($replacements)
67    {
68        if (!($replacements instanceof Swift_Plugins_Decorator_Replacements)) {
69            $this->replacements = (array) $replacements;
70        } else {
71            $this->replacements = $replacements;
72        }
73    }
74
75    /**
76     * Invoked immediately before the Message is sent.
77     *
78     * @param Swift_Events_SendEvent $evt
79     */
80    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
81    {
82        $message = $evt->getMessage();
83        $this->restoreMessage($message);
84        $to = array_keys($message->getTo());
85        $address = array_shift($to);
86        if ($replacements = $this->getReplacementsFor($address)) {
87            $body = $message->getBody();
88            $search = array_keys($replacements);
89            $replace = array_values($replacements);
90            $bodyReplaced = str_replace(
91                $search, $replace, $body
92                );
93            if ($body != $bodyReplaced) {
94                $this->originalBody = $body;
95                $message->setBody($bodyReplaced);
96            }
97
98            foreach ($message->getHeaders()->getAll() as $header) {
99                $body = $header->getFieldBodyModel();
100                $count = 0;
101                if (is_array($body)) {
102                    $bodyReplaced = array();
103                    foreach ($body as $key => $value) {
104                        $count1 = 0;
105                        $count2 = 0;
106                        $key = is_string($key) ? str_replace($search, $replace, $key, $count1) : $key;
107                        $value = is_string($value) ? str_replace($search, $replace, $value, $count2) : $value;
108                        $bodyReplaced[$key] = $value;
109
110                        if (!$count && ($count1 || $count2)) {
111                            $count = 1;
112                        }
113                    }
114                } elseif (is_string($body)) {
115                    $bodyReplaced = str_replace($search, $replace, $body, $count);
116                }
117
118                if ($count) {
119                    $this->originalHeaders[$header->getFieldName()] = $body;
120                    $header->setFieldBodyModel($bodyReplaced);
121                }
122            }
123
124            $children = (array) $message->getChildren();
125            foreach ($children as $child) {
126                list($type) = sscanf($child->getContentType(), '%[^/]/%s');
127                if ('text' == $type) {
128                    $body = $child->getBody();
129                    $bodyReplaced = str_replace(
130                        $search, $replace, $body
131                        );
132                    if ($body != $bodyReplaced) {
133                        $child->setBody($bodyReplaced);
134                        $this->originalChildBodies[$child->getId()] = $body;
135                    }
136                }
137            }
138            $this->lastMessage = $message;
139        }
140    }
141
142    /**
143     * Find a map of replacements for the address.
144     *
145     * If this plugin was provided with a delegate instance of
146     * {@link Swift_Plugins_Decorator_Replacements} then the call will be
147     * delegated to it.  Otherwise, it will attempt to find the replacements
148     * from the array provided in the constructor.
149     *
150     * If no replacements can be found, an empty value (NULL) is returned.
151     *
152     * @param string $address
153     *
154     * @return array
155     */
156    public function getReplacementsFor($address)
157    {
158        if ($this->replacements instanceof Swift_Plugins_Decorator_Replacements) {
159            return $this->replacements->getReplacementsFor($address);
160        }
161
162        return isset($this->replacements[$address]) ? $this->replacements[$address] : null;
163    }
164
165    /**
166     * Invoked immediately after the Message is sent.
167     *
168     * @param Swift_Events_SendEvent $evt
169     */
170    public function sendPerformed(Swift_Events_SendEvent $evt)
171    {
172        $this->restoreMessage($evt->getMessage());
173    }
174
175    /** Restore a changed message back to its original state */
176    private function restoreMessage(Swift_Mime_SimpleMessage $message)
177    {
178        if ($this->lastMessage === $message) {
179            if (isset($this->originalBody)) {
180                $message->setBody($this->originalBody);
181                $this->originalBody = null;
182            }
183            if (!empty($this->originalHeaders)) {
184                foreach ($message->getHeaders()->getAll() as $header) {
185                    if (array_key_exists($header->getFieldName(), $this->originalHeaders)) {
186                        $header->setFieldBodyModel($this->originalHeaders[$header->getFieldName()]);
187                    }
188                }
189                $this->originalHeaders = array();
190            }
191            if (!empty($this->originalChildBodies)) {
192                $children = (array) $message->getChildren();
193                foreach ($children as $child) {
194                    $id = $child->getId();
195                    if (array_key_exists($id, $this->originalChildBodies)) {
196                        $child->setBody($this->originalChildBodies[$id]);
197                    }
198                }
199                $this->originalChildBodies = array();
200            }
201            $this->lastMessage = null;
202        }
203    }
204}
205