/** * Dispatches a URI for internal processing. * Usually called by a front controller. * @method dispatch * @static * @param {mixed} [$uri=null] You can pass a custom URI to dispatch. Otherwise, Qbix will attempt * to route the requested URL, if any. * @param {array} [$check=array('accessible')] Pass array() to skip checking whether the URI can be obtained * as a result of routing some URL. * @return {boolean} * @throws {Q_Exception_MethodNotSupported} * @throws {Q_Exception_Recursion} * @throws {Q_Exception_DispatcherErrors} * @throws {Q_Exception_DispatcherForward} */ static function dispatch($uri = null, $check = array('accessible')) { if (!is_array($check)) { $check = array('accessible'); } if (isset($uri)) { if (in_array('accessible', $check)) { if (!Q_Uri::url($uri)) { // We shouldn't dispatch to this URI $uri = Q_Uri::from(array()); } } self::$uri = Q_Uri::from($uri); } else { $request_uri = Q_Request::uri(); self::$uri = clone $request_uri; } // if file or dir is requested, try to serve it $served = false; $skip = Q_Config::get('Q', 'dispatcherSkipFilename', false); $filename = $skip ? false : Q_Request::filename(); if ($filename) { if (is_dir($filename)) { /** * @event Q/dir * @param {string} filename * @param {string} routed_uri * @return {boolean} */ $served = Q::event("Q/dir", compact('filename', 'routed_uri')); $dir_was_served = true; } else { /** * @event Q/file * @param {string} filename * @param {string} routed_uri * @return {boolean} */ $served = Q::event("Q/file", compact('filename', 'routed_uri')); $dir_was_served = false; } } // if response was served, then return if ($served) { self::result($dir_was_served ? "Dir served" : "File served"); return true; } // This loop is for forwarding $max_forwards = Q_Config::get('Q', 'maxForwards', 10); for ($try = 0; $try < $max_forwards; ++$try) { // Make an array from the routed URI $routed_uri_array = array(); if (self::$uri instanceof Q_Uri) { $routed_uri_array = self::$uri->toArray(); } // If no module was found, then respond with noModule and return if (!isset(self::$uri->module)) { /** * @event Q/noModule * @param {array} $routed_uri_array */ Q::event("Q/noModule", $routed_uri_array); // should echo things self::result("No module"); return false; } $module = self::$uri->module; try { // Implement restricting of modules we are allowed to access $routed_modules = Q_Config::get('Q', 'routedModules', null); if (isset($routed_modules)) { if (!in_array($module, $routed_modules)) { /** * @event Q/notFound * @param {array} $routed_uri_array */ Q::event('Q/notFound', $routed_uri_array); // should echo things self::result("Unknown module"); return false; } } else { if (!Q::realPath("handlers/{$module}")) { Q::event('Q/notFound', $routed_uri_array); // should echo things self::result("Unknown module"); return false; } } // Implement notFound if action was not found if (empty(self::$uri->action)) { Q::event('Q/notFound', $routed_uri_array); // should echo things self::result("Unknown action"); return false; } // Fire a pure event, for aggregation etc if (!isset(self::$skip['Q/prepare'])) { /** * @event Q/prepare * @param {array} $routed_uri_array */ Q::event('Q/prepare', $routed_uri_array, true); } // Perform validation if (!isset(self::$skip['Q/validate'])) { /** * @event Q/validate * @param {array} $routed_uri_array */ Q::event('Q/validate', $routed_uri_array); if (!isset(self::$skip['Q/errors'])) { // Check if any errors accumulated if (Q_Response::getErrors()) { // There were validation errors -- render a response self::result('Validation errors'); self::errors(null, $module, null); return false; } } } // Time to instantiate some app objects from the request if (!isset(self::$skip['Q/objects'])) { /** * @event Q/objects * @param {array} $routed_uri_array */ Q::event('Q/objects', $routed_uri_array, true); } // We might want to reroute the request if (!isset(self::$skip['Q/reroute'])) { /** * @event Q/reroute * @param {array} $routed_uri_array * @return {boolean} whether to stop the dispatch */ $stop_dispatch = Q::event('Q/reroute', $routed_uri_array, true); if ($stop_dispatch) { self::result("Stopped dispatch"); return false; } } // Make some changes to server state, possibly $method = Q_Request::method(); if ($method != 'GET') { $methods = Q_Config::get('Q', 'methods', array('POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD')); if (!in_array($method, $methods)) { throw new Q_Exception_MethodNotSupported(compact('method')); } $method_event = 'Q/' . strtolower($method); if (!isset(self::$skip['Q/method']) and !isset(self::$skip[$method_event])) { if (!Q::canHandle($method_event)) { throw new Q_Exception_MethodNotSupported(compact('method')); } Q::event($method_event); } } // You can calculate some analytics here, and store them somewhere if (!isset(self::$skip['Q/analytics'])) { /** * @event Q/analytics * @param {array} $routed_uri_array */ Q::event('Q/analytics', $routed_uri_array, true); } if (!isset(self::$skip['Q/errors'])) { // Check if any errors accumulated if (Q_Response::getErrors()) { // There were processing errors -- render a response self::result('Processing errors'); self::errors(null, $module, null); return false; } } // When handling all further events, you should probably // refrain from changing server state, and only do reading. // That is because GET in HTTP is not supposed to have side effects // for which the client is responsible. // Start buffering the response, unless otherwise requested $handler = Q_Response::isBuffered(); if ($handler !== false) { $ob = new Q_OutputBuffer($handler); } // Generate and render a response /** * @event Q/response * @param {array} $routed_uri_array */ self::$response_started = true; Q::event("Q/response", $routed_uri_array); if (!empty($ob)) { $ob->endFlush(); } self::result("Served response"); return true; } catch (Q_Exception_DispatcherForward $e) { if (!empty($ob)) { $ob->getClean(); } self::handleForwardException($e); } catch (Q_Exception_DispatcherErrors $e) { if (!empty($ob)) { $partial_response = $ob->getClean(); } else { $partial_response = null; } self::errors(null, $module, $partial_response); self::result("Rendered errors"); return true; } catch (Exception $exception) { if (!empty($ob)) { $partial_response = $ob->getClean(); } else { $partial_response = null; } $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); if (is_callable(array($exception, 'getTraceAsStringEx'))) { $trace_string = $exception->getTraceAsStringEx(); } else { $trace_string = $exception->getTraceAsString(); } $colored = Q_Exception::coloredString($message, $file, $line, $trace_string); self::result("Exception occurred:\n\n{$colored}"); try { self::errors($exception, $module, $partial_response); } catch (Exception $e) { if (!empty($forwarding_to_error_action)) { // Looks like there were errors in the error action // So show the default one with the original exception throw $exception; } if (get_class($e) === 'Q_Exception_DispatcherForward') { $forwarding_to_error_action = true; self::handleForwardException($e); continue; } else { throw $e; } } return false; } } // If we are here, we have done forwarding too much throw new Q_Exception_Recursion(array('function_name' => 'Dispatcher::forward()')); }
function Q_exception_native($params) { extract($params); /** * @var Exception $exception */ if ($is_ajax = Q_Request::isAjax()) { $json = @Q::json_encode(array('errors' => Q_Exception::toArray(array($exception)))); $callback = Q_Request::callback(); switch (strtolower($is_ajax)) { case 'iframe': // Render an HTML layout for ajax if (!Q_Response::$batch) { header("Content-type: text/html"); } echo <<<EOT <!doctype html><html lang=en> <head><meta charset=utf-8><title>Q Result</title></head> <body> <script type="text/javascript"> window.result = function () { return {$json} }; </script> </body> </html> EOT; break; case 'json': // Render a JSON layout for ajax // Render a JSON layout for ajax default: header("Content-type: " . ($callback ? "application/javascript" : "application/json")); echo $callback ? "{$callback}({$json})" : $json; } } else { if (Q::textMode()) { echo Q_Exception::coloredString($exception); exit; } $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); if (is_callable(array($exception, 'getTraceAsStringEx'))) { $trace_string = $exception->getTraceAsStringEx(); } else { $trace_string = $exception->getTraceAsString(); } if ($exception instanceof Q_Exception_PhpError or !empty($exception->messageIsHtml)) { // do not sanitize $message } else { $message = Q_Html::text($message); } $content = "<h1 class='exception_message'>{$message}</h1>"; if (Q_Config::get('Q', 'exception', 'showFileAndLine', true)) { $content .= "<h3 class='exception_fileAndLine'>in {$file} ({$line})</h3>"; } if (Q_Config::get('Q', 'exception', 'showTrace', true)) { $content .= "<pre class='exception_trace'>{$trace_string}</pre>"; } $content .= str_repeat(' ', 512); // because of chrome $title = "Exception occurred"; $dashboard = ""; echo Q::view('Q/layout/html.php', compact('content', 'dashboard', 'title')); } $app = Q_Config::get('Q', 'app', null); $colored = Q_Exception::coloredString($exception); Q::log("{$app}: Exception in " . ceil(Q::milliseconds()) . "ms:\n\n{$colored}\n", null, true, array('maxLength' => 10000)); }
/** * Error handler * @method errorHandler * @param {integer} $errno * @param {string} $errstr * @param {string} $errfile * @param {integer} $errline * @param {array} $errcontext */ static function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) { if (!(error_reporting() & $errno)) { // This error code is not included in error_reporting // just continue on with execution, if possible. // this situation can also happen when // someone has used the @ operator. return; } switch ($errno) { case E_WARNING: case E_NOTICE: case E_USER_WARNING: case E_USER_NOTICE: $context = Q::var_dump($errcontext, 4, '$', 'text'); $dump = Q_Exception::coloredString($errstr, $errfile, $errline, $context); Q::log("PHP Error({$errno}): \n\n{$dump}", null, null, array('maxLength' => 10000)); $type = 'warning'; break; default: $type = 'error'; break; } /** * @event Q/error * @param {integer} errno * @param {string} errstr * @param {string} errfile * @param {integer} errline * @param {array} errcontext */ self::event("Q/error", compact('type', 'errno', 'errstr', 'errfile', 'errline', 'errcontext')); }