fix travis build
[GitHub/Stricted/Domain-Control-Panel.git] / vendor / Zend / Mvc / View / Console / RouteNotFoundStrategy.php
CommitLineData
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
10namespace Zend\Mvc\View\Console;
11
12use Zend\Console\Adapter\AdapterInterface as ConsoleAdapter;
13use Zend\Console\ColorInterface;
14use Zend\Console\Response as ConsoleResponse;
15use Zend\Console\Request as ConsoleRequest;
16use Zend\EventManager\AbstractListenerAggregate;
17use Zend\EventManager\EventManagerInterface;
18use Zend\ModuleManager\ModuleManagerInterface;
19use Zend\ModuleManager\Feature\ConsoleBannerProviderInterface;
20use Zend\ModuleManager\Feature\ConsoleUsageProviderInterface;
21use Zend\Mvc\Application;
22use Zend\Mvc\Exception\RuntimeException;
23use Zend\Mvc\MvcEvent;
24use Zend\ServiceManager\Exception\ServiceNotFoundException;
25use Zend\Stdlib\ResponseInterface as Response;
26use Zend\Stdlib\StringUtils;
27use Zend\Text\Table;
28use Zend\Version\Version;
29use Zend\View\Model\ConsoleModel;
30
31class RouteNotFoundStrategy extends AbstractListenerAggregate
32{
33 /**
34 * Whether or not to display the reason for routing failure
35 *
36 * @var bool
37 */
38 protected $displayNotFoundReason = true;
39
40 /**
41 * The reason for a not-found condition
42 *
43 * @var bool|string
44 */
45 protected $reason = false;
46
47 /**
48 * {@inheritDoc}
49 */
50 public function attach(EventManagerInterface $events, $priority = 1)
51 {
52 $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, [$this, 'handleRouteNotFoundError']);
53 }
54
55 /**
56 * Set flag indicating whether or not to display the routing failure
57 *
58 * @param bool $displayNotFoundReason
59 * @return RouteNotFoundStrategy
60 */
61 public function setDisplayNotFoundReason($displayNotFoundReason)
62 {
63 $this->displayNotFoundReason = (bool) $displayNotFoundReason;
64 return $this;
65 }
66
67 /**
68 * Do we display the routing failure?
69 *
70 * @return bool
71 */
72 public function displayNotFoundReason()
73 {
74 return $this->displayNotFoundReason;
75 }
76
77 /**
78 * Detect if an error is a route not found condition
79 *
80 * If a "controller not found" or "invalid controller" error type is
81 * encountered, sets the response status code to 404.
82 *
83 * @param MvcEvent $e
84 * @throws RuntimeException
85 * @throws ServiceNotFoundException
86 * @return void
87 */
88 public function handleRouteNotFoundError(MvcEvent $e)
89 {
90 $error = $e->getError();
91 if (empty($error)) {
92 return;
93 }
94
95 $response = $e->getResponse();
96 $request = $e->getRequest();
97
98 switch ($error) {
99 case Application::ERROR_CONTROLLER_NOT_FOUND:
100 case Application::ERROR_CONTROLLER_INVALID:
101 case Application::ERROR_ROUTER_NO_MATCH:
102 $this->reason = $error;
103 if (!$response) {
104 $response = new ConsoleResponse();
105 $e->setResponse($response);
106 }
107 $response->setMetadata('error', $error);
108 break;
109 default:
110 return;
111 }
112
113 $result = $e->getResult();
114 if ($result instanceof Response) {
115 // Already have a response as the result
116 return;
117 }
118
119 // Prepare Console View Model
120 $model = new ConsoleModel();
121 $model->setErrorLevel(1);
122
123 // Fetch service manager
124 $sm = $e->getApplication()->getServiceManager();
125
126 // Try to fetch module manager
127 $mm = null;
128 try {
129 $mm = $sm->get('ModuleManager');
130 } catch (ServiceNotFoundException $exception) {
131 // The application does not have or use module manager, so we cannot use it
132 }
133
134 // Try to fetch current console adapter
135 try {
136 $console = $sm->get('console');
137 if (!$console instanceof ConsoleAdapter) {
138 throw new ServiceNotFoundException();
139 }
140 } catch (ServiceNotFoundException $exception) {
141 // The application does not have console adapter
142 throw new RuntimeException('Cannot access Console adapter - is it defined in ServiceManager?');
143 }
144
145 // Retrieve the script's name (entry point)
146 $scriptName = '';
147 if ($request instanceof ConsoleRequest) {
148 $scriptName = basename($request->getScriptName());
149 }
150
151 // Get application banner
152 $banner = $this->getConsoleBanner($console, $mm);
153
154 // Get application usage information
155 $usage = $this->getConsoleUsage($console, $scriptName, $mm);
156
157 // Inject the text into view
158 $result = $banner ? rtrim($banner, "\r\n") : '';
159 $result .= $usage ? "\n\n" . trim($usage, "\r\n") : '';
160 $result .= "\n"; // to ensure we output a final newline
161 $result .= $this->reportNotFoundReason($e);
162 $model->setResult($result);
163
164 // Inject the result into MvcEvent
165 $e->setResult($model);
166 }
167
168 /**
169 * Build Console application banner text by querying currently loaded
170 * modules.
171 *
172 * @param ModuleManagerInterface $moduleManager
173 * @param ConsoleAdapter $console
174 * @return string
175 */
176 protected function getConsoleBanner(ConsoleAdapter $console, ModuleManagerInterface $moduleManager = null)
177 {
178 /*
179 * Loop through all loaded modules and collect banners
180 */
181 $banners = [];
182 if ($moduleManager !== null) {
183 foreach ($moduleManager->getLoadedModules(false) as $module) {
184 // Strict-type on ConsoleBannerProviderInterface, or duck-type
185 // on the method it defines
186 if (!$module instanceof ConsoleBannerProviderInterface
187 && !method_exists($module, 'getConsoleBanner')
188 ) {
189 continue; // this module does not provide a banner
190 }
191
192 // Don't render empty completely empty lines
193 $banner = $module->getConsoleBanner($console);
194 if ($banner == '') {
195 continue;
196 }
197
198 // We colorize each banners in blue for visual emphasis
199 $banners[] = $console->colorize($banner, ColorInterface::BLUE);
200 }
201 }
202
203 /*
204 * Handle an application with no defined banners
205 */
206 if (!count($banners)) {
207 return sprintf("Zend Framework %s application\nUsage:\n", Version::VERSION);
208 }
209
210 /*
211 * Join the banners by a newline character
212 */
213 return implode("\n", $banners);
214 }
215
216 /**
217 * Build Console usage information by querying currently loaded modules.
218 *
219 * @param ConsoleAdapter $console
220 * @param string $scriptName
221 * @param ModuleManagerInterface $moduleManager
222 * @return string
223 * @throws RuntimeException
224 */
225 protected function getConsoleUsage(
226 ConsoleAdapter $console,
227 $scriptName,
228 ModuleManagerInterface $moduleManager = null
229 ) {
230 /*
231 * Loop through all loaded modules and collect usage info
232 */
233 $usageInfo = [];
234
235 if ($moduleManager !== null) {
236 foreach ($moduleManager->getLoadedModules(false) as $name => $module) {
237 // Strict-type on ConsoleUsageProviderInterface, or duck-type
238 // on the method it defines
239 if (!$module instanceof ConsoleUsageProviderInterface
240 && !method_exists($module, 'getConsoleUsage')
241 ) {
242 continue; // this module does not provide usage info
243 }
244
245 // We prepend the usage by the module name (printed in red), so that each module is
246 // clearly visible by the user
247 $moduleName = sprintf(
248 "%s\n%s\n%s\n",
249 str_repeat('-', $console->getWidth()),
250 $name,
251 str_repeat('-', $console->getWidth())
252 );
253
254 $moduleName = $console->colorize($moduleName, ColorInterface::RED);
255
256 $usage = $module->getConsoleUsage($console);
257
258 // Normalize what we got from the module or discard
259 if (is_array($usage) && !empty($usage)) {
260 array_unshift($usage, $moduleName);
261 $usageInfo[$name] = $usage;
262 } elseif (is_string($usage) && ($usage != '')) {
263 $usageInfo[$name] = [$moduleName, $usage];
264 }
265 }
266 }
267
268 /*
269 * Handle an application with no usage information
270 */
271 if (!count($usageInfo)) {
272 // TODO: implement fetching available console routes from router
273 return '';
274 }
275
276 /*
277 * Transform arrays in usage info into columns, otherwise join everything together
278 */
279 $result = '';
280 $table = false;
281 $tableCols = 0;
282 $tableType = 0;
283 foreach ($usageInfo as $moduleName => $usage) {
284 if (!is_string($usage) && !is_array($usage)) {
285 throw new RuntimeException(sprintf(
286 'Cannot understand usage info for module "%s"',
287 $moduleName
288 ));
289 }
290
291 if (is_string($usage)) {
292 // It's a plain string - output as is
293 $result .= $usage . "\n";
294 continue;
295 }
296
297 // It's an array, analyze it
298 foreach ($usage as $a => $b) {
299 /*
300 * 'invocation method' => 'explanation'
301 */
302 if (is_string($a) && is_string($b)) {
303 if (($tableCols !== 2 || $tableType != 1) && $table !== false) {
304 // render last table
305 $result .= $this->renderTable($table, $tableCols, $console->getWidth());
306 $table = false;
307
308 // add extra newline for clarity
309 $result .= "\n";
310 }
311
312 // Colorize the command
313 $a = $console->colorize($scriptName . ' ' . $a, ColorInterface::GREEN);
314
315 $tableCols = 2;
316 $tableType = 1;
317 $table[] = [$a, $b];
318 continue;
319 }
320
321 /*
322 * array('--param', '--explanation')
323 */
324 if (is_array($b)) {
325 if ((count($b) != $tableCols || $tableType != 2) && $table !== false) {
326 // render last table
327 $result .= $this->renderTable($table, $tableCols, $console->getWidth());
328 $table = false;
329
330 // add extra newline for clarity
331 $result .= "\n";
332 }
333
334 $tableCols = count($b);
335 $tableType = 2;
336 $table[] = $b;
337 continue;
338 }
339
340 /*
341 * 'A single line of text'
342 */
343 if ($table !== false) {
344 // render last table
345 $result .= $this->renderTable($table, $tableCols, $console->getWidth());
346 $table = false;
347
348 // add extra newline for clarity
349 $result .= "\n";
350 }
351
352 $tableType = 0;
353 $result .= $b . "\n";
354 }
355 }
356
357 // Finish last table
358 if ($table !== false) {
359 $result .= $this->renderTable($table, $tableCols, $console->getWidth());
360 }
361
362 return $result;
363 }
364
365 /**
366 * Render a text table containing the data provided, that will fit inside console window's width.
367 *
368 * @param $data
369 * @param $cols
370 * @param $consoleWidth
371 * @return string
372 */
373 protected function renderTable($data, $cols, $consoleWidth)
374 {
375 $result = '';
376 $padding = 2;
377
378
379 // If there is only 1 column, just concatenate it
380 if ($cols == 1) {
381 foreach ($data as $row) {
382 if (! isset($row[0])) {
383 continue;
384 }
385 $result .= $row[0] . "\n";
386 }
387 return $result;
388 }
389
390 // Get the string wrapper supporting UTF-8 character encoding
391 $strWrapper = StringUtils::getWrapper('UTF-8');
392
393 // Determine max width for each column
394 $maxW = [];
395 for ($x = 1; $x <= $cols; $x += 1) {
396 $maxW[$x] = 0;
397 foreach ($data as $row) {
398 $maxW[$x] = max($maxW[$x], $strWrapper->strlen($row[$x-1]) + $padding * 2);
399 }
400 }
401
402 /*
403 * Check if the sum of x-1 columns fit inside console window width - 10
404 * chars. If columns do not fit inside console window, then we'll just
405 * concatenate them and output as is.
406 */
407 $width = 0;
408 for ($x = 1; $x < $cols; $x += 1) {
409 $width += $maxW[$x];
410 }
411
412 if ($width >= $consoleWidth - 10) {
413 foreach ($data as $row) {
414 $result .= implode(" ", $row) . "\n";
415 }
416 return $result;
417 }
418
419 /*
420 * Use Zend\Text\Table to render the table.
421 * The last column will use the remaining space in console window
422 * (minus 1 character to prevent double wrapping at the edge of the
423 * screen).
424 */
425 $maxW[$cols] = $consoleWidth - $width -1;
426 $table = new Table\Table();
427 $table->setColumnWidths($maxW);
428 $table->setDecorator(new Table\Decorator\Blank());
429 $table->setPadding(2);
430
431 foreach ($data as $row) {
432 $table->appendRow($row);
433 }
434
435 return $table->render();
436 }
437
438 /**
439 * Report the 404 reason and/or exceptions
440 *
441 * @param \Zend\EventManager\EventInterface $e
442 * @return string
443 */
444 protected function reportNotFoundReason($e)
445 {
446 if (!$this->displayNotFoundReason()) {
447 return '';
448 }
449 $exception = $e->getParam('exception', false);
450 if (!$exception && !$this->reason) {
451 return '';
452 }
453
454 $reason = (!empty($this->reason)) ? $this->reason : 'unknown';
455 $reasons = [
456 Application::ERROR_CONTROLLER_NOT_FOUND => 'Could not match to a controller',
457 Application::ERROR_CONTROLLER_INVALID => 'Invalid controller specified',
458 Application::ERROR_ROUTER_NO_MATCH => 'Invalid arguments or no arguments provided',
459 'unknown' => 'Unknown',
460 ];
461 $report = sprintf("\nReason for failure: %s\n", $reasons[$reason]);
462
463 // @TODO clean up once PHP 7 requirement is enforced
464 while ($exception instanceof \Exception || $exception instanceof \Throwable) {
465 $report .= sprintf("Exception: %s\nTrace:\n%s\n", $exception->getMessage(), $exception->getTraceAsString());
466 $exception = $exception->getPrevious();
467 }
468 return $report;
469 }
470}