Commit | Line | Data |
---|---|---|
44d399bc S |
1 | <?php |
2 | /** | |
3 | * Zend Framework (http://framework.zend.com/) | |
4 | * | |
5 | * @link http://github.com/zendframework/zf2 for the canonical source repository | |
6 | * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) | |
7 | * @license http://framework.zend.com/license/new-bsd New BSD License | |
8 | */ | |
9 | ||
10 | namespace Zend\Mvc\View\Http; | |
11 | ||
12 | use Zend\EventManager\AbstractListenerAggregate; | |
13 | use Zend\EventManager\EventManagerInterface; | |
14 | use Zend\Http\Response as HttpResponse; | |
15 | use Zend\Mvc\Application; | |
16 | use Zend\Mvc\MvcEvent; | |
17 | use Zend\Stdlib\ResponseInterface as Response; | |
18 | use Zend\View\Model\ViewModel; | |
19 | ||
20 | class RouteNotFoundStrategy extends AbstractListenerAggregate | |
21 | { | |
22 | /** | |
23 | * Whether or not to display exceptions related to the 404 condition | |
24 | * | |
25 | * @var bool | |
26 | */ | |
27 | protected $displayExceptions = false; | |
28 | ||
29 | /** | |
30 | * Whether or not to display the reason for a 404 | |
31 | * | |
32 | * @var bool | |
33 | */ | |
34 | protected $displayNotFoundReason = false; | |
35 | ||
36 | /** | |
37 | * Template to use to report page not found conditions | |
38 | * | |
39 | * @var string | |
40 | */ | |
41 | protected $notFoundTemplate = 'error'; | |
42 | ||
43 | /** | |
44 | * The reason for a not-found condition | |
45 | * | |
46 | * @var false|string | |
47 | */ | |
48 | protected $reason = false; | |
49 | ||
50 | /** | |
51 | * {@inheritDoc} | |
52 | */ | |
53 | public function attach(EventManagerInterface $events, $priority = 1) | |
54 | { | |
55 | $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, [$this, 'prepareNotFoundViewModel'], -90); | |
56 | $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, [$this, 'detectNotFoundError']); | |
57 | $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, [$this, 'prepareNotFoundViewModel']); | |
58 | } | |
59 | ||
60 | /** | |
61 | * Set value indicating whether or not to display exceptions related to a not-found condition | |
62 | * | |
63 | * @param bool $displayExceptions | |
64 | * @return RouteNotFoundStrategy | |
65 | */ | |
66 | public function setDisplayExceptions($displayExceptions) | |
67 | { | |
68 | $this->displayExceptions = (bool) $displayExceptions; | |
69 | return $this; | |
70 | } | |
71 | ||
72 | /** | |
73 | * Should we display exceptions related to a not-found condition? | |
74 | * | |
75 | * @return bool | |
76 | */ | |
77 | public function displayExceptions() | |
78 | { | |
79 | return $this->displayExceptions; | |
80 | } | |
81 | ||
82 | /** | |
83 | * Set value indicating whether or not to display the reason for a not-found condition | |
84 | * | |
85 | * @param bool $displayNotFoundReason | |
86 | * @return RouteNotFoundStrategy | |
87 | */ | |
88 | public function setDisplayNotFoundReason($displayNotFoundReason) | |
89 | { | |
90 | $this->displayNotFoundReason = (bool) $displayNotFoundReason; | |
91 | return $this; | |
92 | } | |
93 | ||
94 | /** | |
95 | * Should we display the reason for a not-found condition? | |
96 | * | |
97 | * @return bool | |
98 | */ | |
99 | public function displayNotFoundReason() | |
100 | { | |
101 | return $this->displayNotFoundReason; | |
102 | } | |
103 | ||
104 | /** | |
105 | * Get template for not found conditions | |
106 | * | |
107 | * @param string $notFoundTemplate | |
108 | * @return RouteNotFoundStrategy | |
109 | */ | |
110 | public function setNotFoundTemplate($notFoundTemplate) | |
111 | { | |
112 | $this->notFoundTemplate = (string) $notFoundTemplate; | |
113 | return $this; | |
114 | } | |
115 | ||
116 | /** | |
117 | * Get template for not found conditions | |
118 | * | |
119 | * @return string | |
120 | */ | |
121 | public function getNotFoundTemplate() | |
122 | { | |
123 | return $this->notFoundTemplate; | |
124 | } | |
125 | ||
126 | /** | |
127 | * Detect if an error is a 404 condition | |
128 | * | |
129 | * If a "controller not found" or "invalid controller" error type is | |
130 | * encountered, sets the response status code to 404. | |
131 | * | |
132 | * @param MvcEvent $e | |
133 | * @return void | |
134 | */ | |
135 | public function detectNotFoundError(MvcEvent $e) | |
136 | { | |
137 | $error = $e->getError(); | |
138 | if (empty($error)) { | |
139 | return; | |
140 | } | |
141 | ||
142 | switch ($error) { | |
143 | case Application::ERROR_CONTROLLER_NOT_FOUND: | |
144 | case Application::ERROR_CONTROLLER_INVALID: | |
145 | case Application::ERROR_ROUTER_NO_MATCH: | |
146 | $this->reason = $error; | |
147 | $response = $e->getResponse(); | |
148 | if (!$response) { | |
149 | $response = new HttpResponse(); | |
150 | $e->setResponse($response); | |
151 | } | |
152 | $response->setStatusCode(404); | |
153 | break; | |
154 | default: | |
155 | return; | |
156 | } | |
157 | } | |
158 | ||
159 | /** | |
160 | * Create and return a 404 view model | |
161 | * | |
162 | * @param MvcEvent $e | |
163 | * @return void | |
164 | */ | |
165 | public function prepareNotFoundViewModel(MvcEvent $e) | |
166 | { | |
167 | $vars = $e->getResult(); | |
168 | if ($vars instanceof Response) { | |
169 | // Already have a response as the result | |
170 | return; | |
171 | } | |
172 | ||
173 | $response = $e->getResponse(); | |
174 | if ($response->getStatusCode() != 404) { | |
175 | // Only handle 404 responses | |
176 | return; | |
177 | } | |
178 | ||
179 | if (!$vars instanceof ViewModel) { | |
180 | $model = new ViewModel(); | |
181 | if (is_string($vars)) { | |
182 | $model->setVariable('message', $vars); | |
183 | } else { | |
184 | $model->setVariable('message', 'Page not found.'); | |
185 | } | |
186 | } else { | |
187 | $model = $vars; | |
188 | if ($model->getVariable('message') === null) { | |
189 | $model->setVariable('message', 'Page not found.'); | |
190 | } | |
191 | } | |
192 | ||
193 | $model->setTemplate($this->getNotFoundTemplate()); | |
194 | ||
195 | // If displaying reasons, inject the reason | |
196 | $this->injectNotFoundReason($model); | |
197 | ||
198 | // If displaying exceptions, inject | |
199 | $this->injectException($model, $e); | |
200 | ||
201 | // Inject controller if we're displaying either the reason or the exception | |
202 | $this->injectController($model, $e); | |
203 | ||
204 | $e->setResult($model); | |
205 | } | |
206 | ||
207 | /** | |
208 | * Inject the not-found reason into the model | |
209 | * | |
210 | * If $displayNotFoundReason is enabled, checks to see if $reason is set, | |
211 | * and, if so, injects it into the model. If not, it injects | |
212 | * Application::ERROR_CONTROLLER_CANNOT_DISPATCH. | |
213 | * | |
214 | * @param ViewModel $model | |
215 | * @return void | |
216 | */ | |
217 | protected function injectNotFoundReason(ViewModel $model) | |
218 | { | |
219 | if (!$this->displayNotFoundReason()) { | |
220 | return; | |
221 | } | |
222 | ||
223 | // no route match, controller not found, or controller invalid | |
224 | if ($this->reason) { | |
225 | $model->setVariable('reason', $this->reason); | |
226 | return; | |
227 | } | |
228 | ||
229 | // otherwise, must be a case of the controller not being able to | |
230 | // dispatch itself. | |
231 | $model->setVariable('reason', Application::ERROR_CONTROLLER_CANNOT_DISPATCH); | |
232 | } | |
233 | ||
234 | /** | |
235 | * Inject the exception message into the model | |
236 | * | |
237 | * If $displayExceptions is enabled, and an exception is found in the | |
238 | * event, inject it into the model. | |
239 | * | |
240 | * @param ViewModel $model | |
241 | * @param MvcEvent $e | |
242 | * @return void | |
243 | */ | |
244 | protected function injectException($model, $e) | |
245 | { | |
246 | if (!$this->displayExceptions()) { | |
247 | return; | |
248 | } | |
249 | ||
250 | $model->setVariable('display_exceptions', true); | |
251 | ||
252 | $exception = $e->getParam('exception', false); | |
253 | ||
254 | // @TODO clean up once PHP 7 requirement is enforced | |
255 | if (!$exception instanceof \Exception && !$exception instanceof \Throwable) { | |
256 | return; | |
257 | } | |
258 | ||
259 | $model->setVariable('exception', $exception); | |
260 | } | |
261 | ||
262 | /** | |
263 | * Inject the controller and controller class into the model | |
264 | * | |
265 | * If either $displayExceptions or $displayNotFoundReason are enabled, | |
266 | * injects the controllerClass from the MvcEvent. It checks to see if a | |
267 | * controller is present in the MvcEvent, and, if not, grabs it from | |
268 | * the route match if present; if a controller is found, it injects it into | |
269 | * the model. | |
270 | * | |
271 | * @param ViewModel $model | |
272 | * @param MvcEvent $e | |
273 | * @return void | |
274 | */ | |
275 | protected function injectController($model, $e) | |
276 | { | |
277 | if (!$this->displayExceptions() && !$this->displayNotFoundReason()) { | |
278 | return; | |
279 | } | |
280 | ||
281 | $controller = $e->getController(); | |
282 | if (empty($controller)) { | |
283 | $routeMatch = $e->getRouteMatch(); | |
284 | if (empty($routeMatch)) { | |
285 | return; | |
286 | } | |
287 | ||
288 | $controller = $routeMatch->getParam('controller', false); | |
289 | if (!$controller) { | |
290 | return; | |
291 | } | |
292 | } | |
293 | ||
294 | $controllerClass = $e->getControllerClass(); | |
295 | $model->setVariable('controller', $controller); | |
296 | $model->setVariable('controller_class', $controllerClass); | |
297 | } | |
298 | } |