/** * URL path relative to current request path. * Note: Designed for use with controllers. * * Usage example: * {url controller="blogs"} # /blogs * {url controller="blogs" action="list"} # /blogs/list * {url controller="blogs" action="edit" id=$blog_id} # /blogs/edit/123 */ function url($params) { // Controller path and name. $controller_path = Request::$controller->request->path; $controller_name = Request::$controller->request->controller; if (is_string($params)) { $path = preg_replace("/[\\?\\&].*/", '', $params); } else { if (is_array($params)) { $controller = $params['controller'] ? $params['controller'] : $controller_path . $controller_name; if ($controller[0] !== '/') { $controller = $controller_path ? "{$controller_path}{$controller}" : "/{$controller}"; } if ($params['action'] && $params['action'] !== Request::$config->app['default_action']) { $action = $params['action']; } // Absolute path (default controller). if ($params['absolute']) { $action = $action ?: Request::$config->app['default_action']; } // Unset all expected params, leaving action args. unset($params['controller']); unset($params['action']); unset($params['absolute']); // If other params have been passed, try to align them with action args. $parts = Controller::get_parts($controller, $controller_path); if (count($params) && @(include_once APP_ROOT . "app/controllers{$parts['path']}{$parts['class']}.php")) { $method_name = strtolower(str_replace('-', '_', $action ? $action : Request::$config->app['default_action'])); if (method_exists($parts['class'], $method_name) && ($method = new ReflectionMethod($parts['class'], $method_name))) { $args = $method->getParameters(); $arg_string = ''; $skipped = 1; foreach ($args as $num => $arg) { if (isset($params[$arg->name])) { $arg_string .= str_repeat('/', $skipped); $arg_string .= $params[$arg->name]; $skipped = 1; } else { $skipped++; } } if ($arg_string) { $action .= $action ? $arg_string : Request::$config->app['default_action'] . $arg_string; } } } if (!$arg_string && $controller == Request::$config->app['default_controller'] && $action == Request::$config->app['default_action']) { $path = '/'; } else { $path = $action ? "{$controller}/{$action}" : $controller; } } } return $path; }
/** * Dispatch a controller/request. */ static function dispatch($params, $render = true) { $config = self::$config; $uri = url($params); // Trigger dispatch event. $uri = trigger('request', 'dispatch', $uri, $config); // Match domain route? if ($config->domain_routes) { if ($_SERVER['HTTP_HOST']) { foreach ((array) $config->domain_routes as $name => $domain_match) { if (preg_match('/' . $domain_match . '$/', $_SERVER['HTTP_HOST'])) { $domain = $name; break; } } } else { if (preg_match('/^\\/?([^\\:\\/]+)[\\:\\/]+/', $uri, $match)) { $name = $match[1]; if ($config->domain_routes[$name]) { $uri = preg_replace('/\\/?' . $name . '[\\:\\/]+/', '/', $uri); $domain = $name; } } } } // Match URI to a route. foreach ($config->routes as $route) { if (!is_array($route)) { throw new Exception('App route is busted'); } $route_uri = $route_exp = array_shift($route); // Limit route by domain? if ($route['domain'] && $domain != $route['domain']) { continue; } // Named binds? if (strpos($route_uri, ':') !== false) { // Create a regular expression to check for valid route, start with controller (special). $route_exp = str_replace(":controller", '?(.*)', $route_uri); // Bind requirements. foreach ((array) $route['requirements'] as $bind_name => $exp) { $route_exp = preg_replace("/:{$bind_name}/", "({$exp})", $route_exp); } // All other binds. $route_exp = preg_replace("/:([^\\/]+)/", '([^/]+)', $route_exp); } // Separator. $route_exp = str_replace('/', '\\/', $route_exp); // Wildcards. $route_exp = str_replace('/*', "/?(.*)", $route_exp); // Check if URI matches route. if (preg_match("/^{$route_exp}\$/", $uri)) { // Find controller in URI? if (strpos($route_uri, ":controller") !== false) { $controller_exp = str_replace(":controller", '?(.*)', $route_uri); $controller_exp = str_replace('/', '\\/', preg_replace("/:([^\\/]+)/", '?[^/]*', $controller_exp)); if (preg_match_all("/^{$controller_exp}/", $uri, $controller_matches)) { $controller_test_path = $controller_matches[1][0]; } } // Make sure we have a place to start with the controller path. if (!$controller_test_path) { $controller_test_path = $route['controller']; } $uri_parts = explode('/', trim($controller_test_path, '/')); // Test URI parts until we find an existing controller (start with domain route name). $test_path = $domain ? "/{$domain}" : ''; for ($i = 0; $i < count($uri_parts); $i++) { $test_path .= '/' . $uri_parts[$i]; // Try to match dir default first. $parts = Controller::get_parts("{$test_path}", $controller_path); if (is_dir(APP_ROOT . "app/controllers{$test_path}")) { $parts_dir = Controller::get_parts("{$test_path}/", $controller_path); if (is_file(APP_ROOT . "app/controllers{$parts_dir['path']}{$parts_dir['class']}.php")) { $parts = $parts_dir; $found = true; } } if ($found || is_file(APP_ROOT . "app/controllers{$parts['path']}{$parts['class']}.php")) { // Controller found. $controller = $parts; $controller_path = $controller['path']; $controller_name = $controller['name']; $controller_class = $controller['class']; // Remove domain route name from route uri? if ($domain) { $controller_is_default = $domain == $controller_name; $replace = preg_replace('/^\\/' . $domain . '/', '', $test_path); } else { $replace = $test_path; } // Rewrite route uri for the rest of the binds. $route_uri = str_replace('/:controller', $replace, $route_uri); break; } // If nothing was found, try the default controller. if ($domain) { if ($i + 1 == count($uri_parts) && $uri_parts[$i] !== $domain) { $test_path = ''; $uri_parts[] = $domain; } } else { if ($i + 1 == count($uri_parts) && $uri_parts[$i] !== $config->app['default_controller']) { $uri_parts[] = $config->app['default_controller']; } } } // Output? if (preg_match('/\\/[^\\/]+\\.([^\\/]+)$/', $uri, $output_matches)) { $output = $output_matches[1]; $uri = substr($uri, 0, strrpos($uri, '.')); } // Find values for all other URI binds. $binds = $route; if (preg_match_all("/:([^\\/]+)/", $route_uri, $bind_matches) || strpos($route_uri, '*') !== false) { $route_exp = $route_uri; // Bind requirements. foreach ((array) $route['requirements'] as $bind_name => $exp) { $route_exp = preg_replace("/:{$bind_name}/", "?({$exp})", $route_exp); } // All other binds. $route_exp = str_replace('/', '\\/', preg_replace("/:([^\\/]+)/", '?([^/]*)', $route_exp)); // Wildcards. $route_exp = str_replace('/*', "/?(.*)", $route_exp); // Pull bind values out of URI. if (preg_match_all("/^{$route_exp}/", $uri, $value_matches)) { foreach ((array) $bind_matches[1] as $key => $bind_name) { $binds[$bind_name] = $value_matches[$key + 1][0]; unset($value_matches[$key + 1]); } // Remaining URI. if ($end_value = array_pop($value_matches)) { $uri_remaining = "/{$end_value[0]}"; } } } // Action bind? $action = $binds['action']; // View bind? $view = $binds['view']; // Unset special parameters. unset($binds['controller']); unset($binds['action']); unset($binds['requirements']); unset($binds['view']); // Check if we have binds left to match to action arguments. if (count($binds) && $controller && $action && @(include_once APP_ROOT . "app/controllers{$controller_path}{$controller_class}.php")) { list($method_name) = explode('.', underscore($action)); if (method_exists($controller_class, $method_name) && ($method = new ReflectionMethod($controller_class, $method_name))) { $args = $method->getParameters(); $arg_string = ''; $skipped = 1; foreach ($args as $num => $arg) { if (isset($binds[$arg->name])) { $arg_string .= str_repeat('/', $skipped); $arg_string .= $binds[$arg->name]; $skipped = 1; } else { $skipped++; } } } } else { $arg_string = preg_replace("/^{$route_exp}/", '', $uri); } // Create action arguments array for action call. if ($arg_string . $uri_remaining != '/') { $args = explode('/', substr($arg_string . $uri_remaining, 1)); } break; } } // Controller route defined. $route = array('domain' => $domain, 'path' => $controller_path ?: "/{$domain}/", 'controller' => $controller_name, 'action' => $action, 'output' => $output, 'args' => $args); // Controller found? if ($controller) { // Require and create controller. require_once APP_ROOT . "app/controllers{$controller_path}{$controller_class}.php"; } else { // Determine view by bind or uri. $route['view'] = $view ?: $uri; // AppController or Controller. $controller_class = class_exists('AppController') ? 'AppController' : 'Controller'; } // Create controller instance. $controller = new $controller_class($route); // Push controller onto stack. self::$controller =& $controller; self::$stack[] = self::$controller; // Trigger new controller event. $controller = trigger('controller', 'new', $controller); // Invoke controller action (method). if (method_exists($controller, $controller->request->action)) { $result = call_user_func_array(array(&$controller, $controller->request->action), (array) $controller->request->args); } else { if (method_exists($controller, '__default')) { $result = call_user_func_array(array(&$controller, '__default'), (array) $controller->request->args); } } // Returned an HTTP status code? if (is_int($result)) { // Redirection? if ($result >= 300 && $result < 400) { exit; } else { if ($result >= 400 && $result < 600) { throw new Exception("{$controller_class}::{$action}() returned code {$result}", $result); } } } elseif ($result) { print $result; exit; } // Render output? if ($render) { $controller->render(); } // Pop controller, return it. return array_pop(self::$stack); }