use zend router
[GitHub/Stricted/Domain-Control-Panel.git] / vendor / Zend / Mvc / Controller / Plugin / Forward.php
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\Controller\Plugin;
11
12 use Zend\EventManager\SharedEventManagerInterface as SharedEvents;
13 use Zend\Mvc\Controller\ControllerManager;
14 use Zend\Mvc\Exception;
15 use Zend\Mvc\InjectApplicationEventInterface;
16 use Zend\Mvc\MvcEvent;
17 use Zend\Mvc\Router\RouteMatch;
18 use Zend\Stdlib\CallbackHandler;
19
20 class Forward extends AbstractPlugin
21 {
22 /**
23 * @var ControllerManager
24 */
25 protected $controllers;
26
27 /**
28 * @var MvcEvent
29 */
30 protected $event;
31
32 /**
33 * @var int
34 */
35 protected $maxNestedForwards = 10;
36
37 /**
38 * @var int
39 */
40 protected $numNestedForwards = 0;
41
42 /**
43 * @var array[]|null
44 */
45 protected $listenersToDetach = null;
46
47 /**
48 * @param ControllerManager $controllers
49 */
50 public function __construct(ControllerManager $controllers)
51 {
52 $this->controllers = $controllers;
53 }
54
55 /**
56 * Set maximum number of nested forwards allowed
57 *
58 * @param int $maxNestedForwards
59 * @return self
60 */
61 public function setMaxNestedForwards($maxNestedForwards)
62 {
63 $this->maxNestedForwards = (int) $maxNestedForwards;
64
65 return $this;
66 }
67
68 /**
69 * Get information on listeners that need to be detached before dispatching.
70 *
71 * Each entry in the array contains three keys:
72 *
73 * id (identifier for event-emitting component),
74 * event (the hooked event)
75 * and class (the class of listener that should be detached).
76 *
77 * @return array
78 */
79 public function getListenersToDetach()
80 {
81 // If a blacklist has not been explicitly set, return the default:
82 if (null === $this->listenersToDetach) {
83 // We need to detach the InjectViewModelListener to prevent templates
84 // from getting attached to the ViewModel twice when a calling action
85 // returns the output generated by a forwarded action.
86 $this->listenersToDetach = [[
87 'id' => 'Zend\Stdlib\DispatchableInterface',
88 'event' => MvcEvent::EVENT_DISPATCH,
89 'class' => 'Zend\Mvc\View\Http\InjectViewModelListener',
90 ]];
91 }
92 return $this->listenersToDetach;
93 }
94
95 /**
96 * Set information on listeners that need to be detached before dispatching.
97 *
98 * @param array $listeners Listener information; see getListenersToDetach() for details on format.
99 *
100 * @return self
101 */
102 public function setListenersToDetach($listeners)
103 {
104 $this->listenersToDetach = $listeners;
105
106 return $this;
107 }
108
109 /**
110 * Dispatch another controller
111 *
112 * @param string $name Controller name; either a class name or an alias used in the controller manager
113 * @param null|array $params Parameters with which to seed a custom RouteMatch object for the new controller
114 * @return mixed
115 * @throws Exception\DomainException if composed controller does not define InjectApplicationEventInterface
116 * or Locator aware; or if the discovered controller is not dispatchable
117 */
118 public function dispatch($name, array $params = null)
119 {
120 $event = clone($this->getEvent());
121
122 $controller = $this->controllers->get($name);
123 if ($controller instanceof InjectApplicationEventInterface) {
124 $controller->setEvent($event);
125 }
126
127 // Allow passing parameters to seed the RouteMatch with & copy matched route name
128 if ($params !== null) {
129 $routeMatch = new RouteMatch($params);
130 $routeMatch->setMatchedRouteName($event->getRouteMatch()->getMatchedRouteName());
131 $event->setRouteMatch($routeMatch);
132 }
133
134 if ($this->numNestedForwards > $this->maxNestedForwards) {
135 throw new Exception\DomainException("Circular forwarding detected: greater than $this->maxNestedForwards nested forwards");
136 }
137 $this->numNestedForwards++;
138
139 // Detach listeners that may cause problems during dispatch:
140 $sharedEvents = $event->getApplication()->getEventManager()->getSharedManager();
141 $listeners = $this->detachProblemListeners($sharedEvents);
142
143 $return = $controller->dispatch($event->getRequest(), $event->getResponse());
144
145 // If we detached any listeners, reattach them now:
146 $this->reattachProblemListeners($sharedEvents, $listeners);
147
148 $this->numNestedForwards--;
149
150 return $return;
151 }
152
153 /**
154 * Detach problem listeners specified by getListenersToDetach() and return an array of information that will
155 * allow them to be reattached.
156 *
157 * @param SharedEvents $sharedEvents Shared event manager
158 * @return array
159 */
160 protected function detachProblemListeners(SharedEvents $sharedEvents)
161 {
162 // Convert the problem list from two-dimensional array to more convenient id => event => class format:
163 $formattedProblems = [];
164 foreach ($this->getListenersToDetach() as $current) {
165 if (!isset($formattedProblems[$current['id']])) {
166 $formattedProblems[$current['id']] = [];
167 }
168 if (!isset($formattedProblems[$current['id']][$current['event']])) {
169 $formattedProblems[$current['id']][$current['event']] = [];
170 }
171 $formattedProblems[$current['id']][$current['event']][] = $current['class'];
172 }
173
174 // Loop through the class blacklist, detaching problem events and remembering their CallbackHandlers
175 // for future reference:
176 $results = [];
177 foreach ($formattedProblems as $id => $eventArray) {
178 $results[$id] = [];
179 foreach ($eventArray as $eventName => $classArray) {
180 $results[$id][$eventName] = [];
181 $events = $this->getSharedListenersById($id, $eventName, $sharedEvents);
182 foreach ($events as $priority => $currentPriorityEvents) {
183 // v2 fix
184 if (!is_array($currentPriorityEvents)) {
185 $currentPriorityEvents = [$currentPriorityEvents];
186 }
187 // v3
188 foreach ($currentPriorityEvents as $currentEvent) {
189 $currentCallback = $currentEvent;
190
191 // zend-eventmanager v2 compatibility:
192 if ($currentCallback instanceof CallbackHandler) {
193 $currentCallback = $currentEvent->getCallback();
194 $priority = $currentEvent->getMetadatum('priority');
195 }
196
197 // If we have an array, grab the object
198 if (is_array($currentCallback)) {
199 $currentCallback = array_shift($currentCallback);
200 }
201
202 // This routine is only valid for object callbacks
203 if (!is_object($currentCallback)) {
204 continue;
205 }
206
207 foreach ($classArray as $class) {
208 if ($currentCallback instanceof $class) {
209 // Pass $currentEvent; when using zend-eventmanager v2,
210 // this is the CallbackHandler, while in v3 it's
211 // the actual listener.
212 $this->detachSharedListener($id, $currentEvent, $sharedEvents);
213 $results[$id][$eventName][$priority] = $currentEvent;
214 }
215 }
216 }
217 }
218 }
219 }
220
221 return $results;
222 }
223
224 /**
225 * Reattach all problem listeners detached by detachProblemListeners(), if any.
226 *
227 * @param SharedEvents $sharedEvents Shared event manager
228 * @param array $listeners Output of detachProblemListeners()
229 * @return void
230 */
231 protected function reattachProblemListeners(SharedEvents $sharedEvents, array $listeners)
232 {
233 foreach ($listeners as $id => $eventArray) {
234 foreach ($eventArray as $eventName => $callbacks) {
235 foreach ($callbacks as $priority => $current) {
236 $callback = $current;
237
238 // zend-eventmanager v2 compatibility:
239 if ($current instanceof CallbackHandler) {
240 $callback = $current->getCallback();
241 $priority = $current->getMetadatum('priority');
242 }
243
244 $sharedEvents->attach($id, $eventName, $callback, $priority);
245 }
246 }
247 }
248 }
249
250 /**
251 * Get the event
252 *
253 * @return MvcEvent
254 * @throws Exception\DomainException if unable to find event
255 */
256 protected function getEvent()
257 {
258 if ($this->event) {
259 return $this->event;
260 }
261
262 $controller = $this->getController();
263 if (!$controller instanceof InjectApplicationEventInterface) {
264 throw new Exception\DomainException(sprintf(
265 'Forward plugin requires a controller that implements InjectApplicationEventInterface; received %s',
266 (is_object($controller) ? get_class($controller) : var_export($controller, 1))
267 ));
268 }
269
270 $event = $controller->getEvent();
271 if (!$event instanceof MvcEvent) {
272 $params = [];
273 if ($event) {
274 $params = $event->getParams();
275 }
276 $event = new MvcEvent();
277 $event->setParams($params);
278 }
279 $this->event = $event;
280
281 return $this->event;
282 }
283
284 /**
285 * Retrieve shared listeners for an event by identifier.
286 *
287 * Varies retrieval based on zend-eventmanager version.
288 *
289 * @param string|int $id
290 * @param string $event
291 * @param SharedEvents $sharedEvents
292 * @return array|\Traversable
293 */
294 private function getSharedListenersById($id, $event, SharedEvents $sharedEvents)
295 {
296 if (method_exists($sharedEvents, 'attachAggregate')) {
297 // v2
298 return $sharedEvents->getListeners($id, $event) ?: [];
299 }
300
301 // v3
302 return $sharedEvents->getListeners([$id], $event);
303 }
304
305 /**
306 * Detach a shared listener by identifier.
307 *
308 * Varies detachment based on zend-eventmanager version.
309 *
310 * @param string|int $id
311 * @param callable|CallbackHandler $listener
312 * @param SharedEvents $sharedEvents
313 * @return void
314 */
315 private function detachSharedListener($id, $listener, SharedEvents $sharedEvents)
316 {
317 if (method_exists($sharedEvents, 'attachAggregate')) {
318 // v2
319 $sharedEvents->detach($id, $listener);
320 return;
321 }
322
323 // v3
324 $sharedEvents->detach($listener, $id);
325 }
326 }