/** * 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()')); }
/** * Dumps the result as a table in text mode * @method __toString */ function __toString() { try { $ob = new Q_OutputBuffer(); $results = array(); foreach ($this->fields as $key => $value) { $results[] = array('Field name:' => $key, 'Field value:' => $value, 'Field modified:' => $this->wasModified($key) ? 'Yes' : 'No'); } Db_Utils::dump_table($results); return $ob->getClean(); } catch (Exception $e) { return $e->getMessage(); } }
/** * Renders a particular view * @method view * @static * @param {string} $viewName * The full name of the view * @param {array} $params=array() * Parameters to pass to the view * @return {string} * The rendered content of the view * @throws {Q_Exception_MissingFile} */ static function view($viewName, $params = array()) { require_once Q_CLASSES_DIR . DS . 'Q' . DS . 'Exception' . DS . 'MissingFile.php'; if (empty($params)) { $params = array(); } $viewName = implode(DS, explode('/', $viewName)); $fields = Q_Config::get('Q', 'views', 'fields', null); if ($fields) { $params = array_merge($fields, $params); } /** * @event {before} Q/view * @param {string} viewName * @param {string} params * @return {string} * Optional. If set, override method return */ $result = self::event('Q/view', compact('viewName', 'params'), 'before'); if (isset($result)) { return $result; } try { $ob = new Q_OutputBuffer(); self::includeFile('views' . DS . $viewName, $params); return $ob->getClean(); } catch (Q_Exception_MissingFile $e) { if (basename($e->params['filename']) != basename($viewName)) { throw $e; } $ob->flushHigherBuffers(); /** * Renders 'Missing View' page * @event Q/missingView * @param {string} viewName * @return {string} */ return self::event('Q/missingView', compact('viewName')); } }
/** * Returns a <style> tag with the content of all the stylesheets included inline * @method stylesheetsInline * @static * @param {array} [$styles=array()] If not empty, this associative array contains styles which will be * included at the end of the generated <style> tag. * @param {string} [$slotName=null] If provided, returns only the stylesheets added while filling this slot. * @return {string} the style tags and their contents inline */ static function stylesheetsInline($styles = array(), $slotName = null) { if (empty(self::$stylesheets)) { return ''; } $sheets = self::stylesheetsArray($slotName, false); $sheets_for_slots = array(); if (!empty($sheets)) { foreach ($sheets as $stylesheet) { $href = ''; $media = 'screen, print'; $type = 'text/css'; extract($stylesheet, EXTR_IF_EXISTS); $ob = new Q_OutputBuffer(); if (Q_Valid::url($href)) { try { include $href; } catch (Exception $e) { } } else { list($href, $filename) = Q_Html::themedUrlAndFilename($href); try { Q::includeFile($filename); } catch (Exception $e) { } } $sheets_for_slots[$stylesheet['slot']][] = "\n/* Included inline from {$href} */\n" . $ob->getClean(); } } $parts = array(); foreach ($sheets_for_slots as $slot => $texts) { $parts[] = Q_Html::tag('style', array('data-slot' => $slot), implode("\n\n", $texts)); } return implode("", $parts); }
/** * The default implementation. */ function Q_errors($params) { extract($params); /** * @var Exception $exception * @var boolean $startedResponse */ if (!empty($exception)) { Q_Response::addError($exception); } $errors = Q_Response::getErrors(); $errors_array = Q_Exception::toArray($errors); // Simply return the errors, if this was an AJAX request if ($is_ajax = Q_Request::isAjax()) { try { $errors_json = @Q::json_encode($errors_array); } catch (Exception $e) { $errors_array = array_slice($errors_array, 0, 1); unset($errors_array[0]['trace']); $errors_json = @Q::json_encode($errors_array); } $json = "{\"errors\": {$errors_json}}"; $callback = Q_Request::callback(); switch (strtolower($is_ajax)) { case 'iframe': 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': default: header("Content-type: " . ($callback ? "application/javascript" : "application/json")); echo $callback ? "{$callback}({$json})" : $json; } return; } // Forward internally, if it was requested if ($onErrors = Q_Request::special('onErrors', null)) { $uri1 = Q_Dispatcher::uri(); $uri2 = Q_Uri::from($onErrors); $url2 = $uri2->toUrl(); if (!isset($uri2)) { throw new Q_Exception_WrongValue(array('field' => 'onErrors', 'range' => 'an internal URI reachable from a URL')); } if ($uri1->toUrl() !== $url2) { Q_Dispatcher::forward($uri2); return; // we don't really need this, but it's here anyway } } $params2 = compact('errors', 'exception', 'errors_array', 'exception_array'); if (Q::eventStack('Q/response')) { // Errors happened while rendering response. Just render errors view. return Q::view('Q/errors.php', $params2); } if (!$startedResponse) { try { // Try rendering the response, expecting it to // display the errors along with the rest. $ob = new Q_OutputBuffer(); Q::event('Q/response', $params2); $ob->endFlush(); return; } catch (Exception $e) { if (get_class($e) === 'Q_Exception_DispatcherForward') { throw $e; // if forwarding was requested, do it // for all other errors, continue trying other things } $output = $ob->getClean(); } } if ($errors) { // Try rendering the app's errors response, if any. $app = Q::app(); if (Q::canHandle("{$app}/errors/response/content")) { Q_Dispatcher::forward("{$app}/errors"); } else { echo Q::view("Q/errors.php", compact('errors')); } } if (!empty($e)) { return Q::event('Q/exception', array('exception' => $e)); } }
/** * Returns a response to the client. * @param {boolean} [$closeConnection=false] Whether to send headers to close the connection * @method response * @static */ static function response($closeConnection = false) { if (self::$servedResponse) { return; // response was served, and no new dispatch started } // Start buffering the response, unless otherwise requested $handler = Q_Response::isBuffered(); if ($handler !== false) { $ob = new Q_OutputBuffer($handler); } if (!empty($_GET['Q_ct'])) { Q_Response::setCookie('Q_ct', $_GET['Q_ct']); } if (!empty($_GET['Q_cordova'])) { Q_Response::setCookie('Q_cordova', $_GET['Q_cordova']); } Q_Response::sendCookieHeaders(); // Generate and render a response /** * Gives the app a chance to generate a response. * You should not change the server state when handling this event. * @event Q/response * @param {array} $routed */ self::$startedResponse = true; Q::event("Q/response", self::$routed); if ($closeConnection) { header("Connection: close"); header("Content-Length: " . $ob->getLength()); } if (!empty($ob)) { $ob->endFlush(); } if ($closeConnection) { ob_end_flush(); flush(); } self::$servedResponse = true; self::result("Served response"); return true; }
/** * Dumps the result as a table in text mode * Side effect, though: can't fetch anymore until the cursor is closed. * @method __toString * @return {string} */ function __toString() { return "Db_Result"; try { $ob = new Q_OutputBuffer(); $rows = $this->fetchAll(PDO::FETCH_ASSOC); Db::dump_table($rows); return $ob->getClean(); } catch (Exception $e) { return $e->getMessage(); } }