/** * Parses a color from a string. Hexadecimal values (HTML/CSS format) are * converted to a color value, e.g. \#ff0000 for red. The alpha channel * (transparency) can be provided as well by adding two more letters, e.g. * \#ff000012. Values consisting of only 3 letters are accepted as well and * interpreted according to the same semantics as HTML/CSS uses. * * The leading # character is optional. * * \param $str * The string to parse. * * \return Allocated color value * * \see AnewtImage::color_from_rgb * \see AnewtImage::color_from_rgba */ public function color_from_string($str) { assert('is_string($str)'); $str = str_strip_prefix($str, '#'); /* Accept CSS-valid values with only 3 letters as well */ if (strlen($str) == 3) { $str = join('', array($str[0], $str[0], $str[1], $str[1], $str[2], $str[2])); } /* The string should have either 6 or 8 characters. */ assert('(strlen($str)==6) || (strlen($str)==8)'); $red = hexdec(substr($str, 0, 2)); $green = hexdec(substr($str, 2, 2)); $blue = hexdec(substr($str, 4, 2)); if (strlen($str) == 6) { $alpha = null; } else { $alpha = hexdec(substr($str, 6, 2)); } return $this->_allocate_color($red, $green, $blue, $alpha); }
/** * Dispatch an URL to the correct handlers. See the documentation on * URLDispatcher::dispatch() for more information on the parameters. * * \param $url * The url to dispatch (optional, defaults to null). * * \see URLDispatcher::dispatch */ function dispatch($url = null) { if (is_null($url)) { $url = AnewtRequest::relative_url(); } assert('is_string($url)'); /* Get the default settings */ $module_name = $this->_getdefault('default-module', null); $class_name = $this->_get('default-class'); $skip_path_components = 0; /* Iterate over the mappings and override the default if the mapping matches */ $test_url = str_strip_prefix($url, '/'); foreach ($this->prefix_to_dispatcher_mapping as $prefix => $mapping) { /* Try to match the prefix. Add a trailing slash, otherwise the url * /newsxyz would also match the /news mapping, which is not * intended behaviour. */ $test_prefix = $prefix . '/'; if (str_has_prefix($test_url, $test_prefix)) { /* The prefix matches the url */ list($module_name, $class_name) = $mapping; $skip_path_components = count(explode('/', $prefix)); break; } } /* Load module (if supplied) */ if (!is_null($module_name)) { $this->load_module($module_name); } /* Create and invoke dispatcher */ $dispatcher =& new $class_name(); $dispatcher->dispatch($url, $skip_path_components); }
/** * Join all parameters into a URL path with an extension. This function does * the same as URL::join, but uses the last parameter as a file extension. * * \param $parts * Any number of string parameters (or an array) * * \param $ext * The extension to append to the path. All strings are accepted and * a leading dot is taken care of, so that both "txt" and ".txt" work * correctly. * * \return * The resulting URL path * * \see URL::join */ static function join_ext($parts, $ext = null) { $args = func_get_args(); $num_args = func_num_args(); /* Accept one single array argument too */ if ($num_args == 1 && is_numeric_array($args[0])) { $args = $args[0]; } /* Require at least 2 parameters */ assert('is_numeric_array($args)'); assert('count($args) >= 2'); $ext = array_pop($args); assert('is_string($ext)'); $ext = str_strip_prefix($ext, '.'); $path = URL::join($args); return sprintf('%s.%s', $path, $ext); }
/** * Dispatch an URL to the correct handlers. This method does the actual * magic, such as url parsing, matching and command invocation. You can * optionally provide a custom url and tell the dispatcher that some parts * of the url should be skipped when handling this request. * * \param $url * The url to dispatch (optional, defaults to null). Omit this value (or * provide null) to use the current request url. * * \param $skip_path_components * The number of path components to skip when handling this request * (optional, defaults to null). This is useful if you want to skip some * prefix present in all urls, such as ~username. If you don't specify * this parameter the value of <code>$_SERVER['PHP_SELF']</code> will be * used to figure out how many components to skip. */ function dispatch($url = null, $skip_path_components = null) { if (is_null($skip_path_components)) { /* Figure out the current script location. This is most likely * the script living in the document root calling this method. Use * the directory path component of this file to figure out how many * path components should be skipped. */ $dir_url = dirname($_SERVER['PHP_SELF']); if ($dir_url == DIRECTORY_SEPARATOR) { $skip_path_components = 0; } else { $skip_path_components = count(explode('/', str_strip_prefix($dir_url, '/'))); } } /* Initialization */ $get_params = ''; /* Use the current url if no explicit url was given */ if (is_null($url)) { $url = Request::relative_url(); } /* We need to strip off the GET parameters */ $question_mark_pos = strpos($url, '?'); if ($question_mark_pos !== false) { $get_params = substr($url, $question_mark_pos); $url = substr($url, 0, $question_mark_pos); } /* Sanity checks */ assert('is_int($skip_path_components) && $skip_path_components >= 0'); assert('is_string($url)'); /* Force trailing slash for GET requests? */ if (Request::is_get() && $this->_getdefault('force-trailing-slash', true) && $url != '/' && !str_has_suffix($url, '/') && preg_match('/^.*\\/[^.\\/]+$/', $url)) { redirect($url . '/' . $get_params, HTTP_STATUS_MOVED_PERMANENTLY); } /* Store the url so that it can be used later */ $this->_set('url', $url); /* Split the url into smaller pieces */ $url = str_strip_prefix($url, '/'); $url = str_strip_suffix($url, '/'); $components = split('/', $url); /* Skip some components if requested, and store the cut-off part in the * 'base-url' property. */ if (count($components) < $skip_path_components) { $this->_handle_result(HTTP_STATUS_INTERNAL_SERVER_ERROR); } $base_url = sprintf('/%s/', join('/', array_slice($components, 0, $skip_path_components))); $this->_set('base-url', $base_url); $components = array_slice($components, $skip_path_components); /* Special case for top level urls */ if (count($components) == 1 && strlen($components[0]) == 0) { $components = array(); } /* Try all URL maps and see if they match the input url */ $found_map = false; $command_name = null; $parameters = array(); foreach ($this->urlmaps as $urlmap) { list($command_name, $patterns) = $urlmap; /* Check for valid parameters */ $match = $this->_match_inputs_with_patterns($components, $patterns); /* This urlmap didn't match, try next one */ if ($match === false) { continue; /* This urlmap matched! */ } else { $parameters = $match; $found_map = true; break; } } /* No explicit map found, try an implicit map */ if (!$found_map && $this->_getdefault('use-implicit-commands', true)) { $command_name = join('_', $components); $command_name = str_replace('-', '_', $command_name); /* The method must exist */ $command = 'command_' . $command_name; $found_map = method_exists($this, $command); } /* As a last resort try the default handler, if one was set. There's no * need to check the availability of the method; set_default_command() * already did that. */ if (!$found_map && $this->_isset('default-command')) { $command_name = $this->_get('default-command'); $found_map = true; } /* Sanity check: is the method available? */ $command = 'command_' . $command_name; if (!method_exists($this, $command)) { /* FIXME: it's not clear if this is desirable */ /* We found a handler name but the method doesn't exist... */ /* Trying the default handler, but remember which command * we wanted to access. */ if (!$this->_isset('default_command')) { /* We give up. */ $found_map = false; } else { $command = 'command_' . $this->_get('default-command'); } } /* If we still don't have a command, we give up. Too bad... not found */ if (!$found_map) { $this->_handle_result(HTTP_STATUS_NOT_FOUND); return false; } /* Store the command name for use by _handle_result() and possibly * pre_command(). */ $this->_set('command', $command_name); /* If this piece of code is reached, a valid command has been found. Run * the pre-command hook, call the appropriate method and handle the * result. The pre_command() method may return HTTP_STATUS_NOT_FOUND or * HTTP_STATUS_FORBIDDEN as well, so we need to handle the status * carefully. */ $status = $this->pre_command($parameters); /* The pre_command() method is not required to have a return value. If * it doesn't return anything, $status will be null at this point. If * that's the case we assume everything is alright. */ if (is_null($status)) { $status = HTTP_STATUS_OK; } if ($status == HTTP_STATUS_OK) { /* The pre_command() method succeeded. Run the actual command and * keep track of the status. */ $status = $this->{$command}($parameters); } /* Regardless of whether the actual command has been executed, the * result handler is invoked. Note: The post_command() is only invoked * if both the pre_command() and the actual command method return * HTTP_STATUS_OK (there's no danger of calling post_command() if no * real command has been executed). */ $this->_handle_result($status); }
/** * Translate a plural message with disambiguating context using the default text * domain. * * This function can be used to provide context to the translator. * * \param $domain * The text domain used for the lookup * \param $ctx * The context of the message. * \param $msgid1 * The singular string to translate. * \param $msgid1 * The plural string to translate. * \param $n * The count * * \return * The translated string, or the original string if no translation was * found. * * \see ngettext * \see dpgettext * \see npgettext */ function dnpgettext($domain, $ctx, $msgid1, $msgid2, $n) { /* See dpgettext() implementation above */ $msgid1_with_ctx = sprintf("%s%s", $ctx, $msgid1); $msgid2_with_ctx = sprintf("%s%s", $ctx, $msgid2); $translated = dngettext($domain, $msgid1_with_ctx, $msgid2_with_ctx, $n); $prefix = sprintf("%s", $ctx); $translated = str_strip_prefix($translated, $prefix); return $translated; }
/** * (Really) dispatch an URL to the correct handlers. * * This method does the actual magic, such as URL parsing, matching and * command invocation. You can optionally provide a custom URL and tell the * dispatcher that some parts of the URL should be skipped when handling * this request. * * \param $url * \param $prefix * \see AnewtURLDispatcher::dispatch */ private function real_dispatch($url = null, $prefix = null) { /* Use the current URL if no explicit url was given */ if (is_null($url)) { $url = AnewtRequest::relative_url(); } /* Figure out the right base location if no prefix was given. If the URL * starts with the PHP script name, we assume no htaccess file has been * setup to beautify the website URLs. In this case the relevant parts * of the URL are added after the PHP script name. Example URL of such * a setup is http://.../dispatch.php/a/b/c/. Otherwise, it is quite * likely a htaccess file is used to point all requests to a script that * invokes the dispatcher. We assume this script is placed in the * toplevel directory, so we use that directory as the prefix. */ if (is_null($prefix)) { if (str_has_prefix($url, $_SERVER['SCRIPT_NAME'])) { $prefix = $_SERVER['SCRIPT_NAME']; } else { $prefix = dirname($_SERVER['SCRIPT_NAME']); } } assert('is_string($url)'); assert('is_string($prefix)'); /* Strip off the GET parameters from the URL */ $get_params = ''; $question_mark_pos = strpos($url, '?'); if ($question_mark_pos !== false) { $get_params = substr($url, $question_mark_pos); $url = substr($url, 0, $question_mark_pos); } /* Redirect GET requests when trailing slash is required but missing */ if (!str_has_suffix($url, '/') && $this->force_trailing_slash && AnewtRequest::is_get() && !preg_match('#^.*\\.[^\\/]*$#', $url)) { redirect(sprintf('%s/%s', $url, $get_params), HTTP_STATUS_MOVED_PERMANENTLY); } /* Strip off prefix and slashes */ $this->request_url_full = $url; $url = str_strip_prefix($url, $prefix); $url = str_strip_prefix($url, '/'); $url = str_strip_suffix($url, '/'); $this->request_url = $url; /* Try to find a matching route and extract the parameters */ $found_route = false; $command = null; $parameters = array(); $url_parts = strlen($url) > 0 ? explode('/', $url) : array(); foreach ($this->routes as $route) { $route_type = array_shift($route); $route_command = array_shift($route); $route_parameters = array(); /* Type I: Routes using regular expression */ if ($route_type == ANEWT_URL_DISPATCHER_ROUTE_TYPE_REGEX) { list($pattern) = $route; /* Try both with and without trailing slash */ if (preg_match($pattern, $url, $route_parameters) || preg_match($pattern, sprintf('%s/', $url), $route_parameters)) { /* We don't care about $parameters[0] (it contains the full match) */ array_shift($route_parameters); $route_parameters = array_map('urldecode', $route_parameters); $command = $route_command; $parameters = $route_parameters; $found_route = true; break; } } elseif ($route_type == ANEWT_URL_DISPATCHER_ROUTE_TYPE_URL_PARTS) { list($route_url, $additional_constraints) = $route; /* Route URL can be a string or an array */ if (is_string($route_url)) { $route_url = str_strip_prefix($route_url, '/'); $route_url = str_strip_suffix($route_url, '/'); $route_url_parts = strlen($route_url) > 0 ? explode('/', $route_url) : array(); } elseif (is_numeric_array($route_url)) { $route_url_parts = $route_url; } else { throw new AnewtException('Invalid url route: %s', $route_url); } /* Match the URL parts against the route URL parts */ if (count($url_parts) != count($route_url_parts)) { continue; } $constraints = array_merge($this->url_part_constraints, $additional_constraints); for ($i = 0; $i < count($url_parts); $i++) { /* If the URL starts with a ':' character it is * a parameter... */ if ($route_url_parts[$i][0] === ':') { $parameter_name = substr($route_url_parts[$i], 1); $parameter_value = $url_parts[$i]; /* If there is a constraint for this parameter, the * value must match the constraint. If not, this route * cannot be used. */ if (array_key_exists($parameter_name, $constraints)) { $pattern = $constraints[$parameter_name]; if (!preg_match($pattern, $parameter_value)) { continue 2; } } $route_parameters[$parameter_name] = urldecode($parameter_value); } elseif ($url_parts[$i] !== $route_url_parts[$i]) { continue 2; } } /* If this code is reached, we found a matching route with all * the constraints on the URL parts satisfied. */ $command = $route_command; $parameters = $route_parameters; $found_route = true; break; } else { assert('false; // not reached'); } } /* If no route matches, try an automatic route. Only the first URL part * is considered for this. */ if (!$found_route && $this->use_automatic_commands) { $url_parts = explode('/', $url, 2); if ($url_parts) { $command = array($this, sprintf('command_%s', $url_parts[0])); list($found_route, $error_message_to_ignore) = $this->is_valid_command($command); } } /* As a last resort try the default handler, if one was set. */ $default_command = $this->default_command; if (!$found_route && !is_null($default_command)) { $command = $default_command; $command = $this->validate_command($command); $found_route = true; } /* If we still don't have a command, we give up. Too bad... not found */ if (!$found_route) { throw new AnewtHTTPException(HTTP_STATUS_NOT_FOUND); } /* Check the command for validity. In most cases we already know the * command exists since that is already checked in the add_route_*() * methods or in the code above, except for lazily loaded commands, so * we try to load them and check for validity afterwards. */ if (is_array($command) && is_string($command[0])) { $this->include_command_class($command[0]); $command = $this->validate_command($command); } /* Finally... run the command and the pre and post command hooks. */ $this->pre_command($parameters); call_user_func($command, $parameters); $this->post_command($parameters); }
/** * Build a URL from the passed parameters. * * You can provide a single path string, or an array of strings, in which * case each of the items in \c $path is a path component. The path * components will be concatenated, separated by slashes. * * All slashes are normalized. If the first path component has a leading * slash, the resulting string will also have a leading slash and if it * doesn't, the resulting string won't have one either. The same goes for * the trailing slash: if the last path component ends with a slash, the * resulting string will have one as well. * * If \c $parameters is passed, a HTTP query string will be appended to the * url using the this associatve array. * * Example: * * <code>$url = AnewtURL::join(array('/path/to', $file), array('foo' => 'bar'));</code> * * \param $path * Single string or array of strings (each item is a path component of the url) * * \param $parameters * Associative array used to build a query string (optional) * * \return * The resulting URL path * * \see * AnewtURL::parse */ static function build($path, $parameters = null) { /* Input sanitizing */ if (is_string($path)) { $path_components = array($path); } else { $path_components = $path; } if (is_null($parameters)) { $parameters = array(); } assert('is_numeric_array($path_components);'); assert('is_assoc_array($parameters);'); /* Remove empty path components */ $path_components = str_all_non_white($path_components); /* Leading and trailing slashes */ if ($path_components) { /* Path is not empty */ $use_leading_slash = str_has_prefix($path_components[0], '/'); $use_trailing_slash = str_has_suffix($path_components[count($path_components) - 1], '/'); } else { /* Path is empty */ $use_leading_slash = false; $use_trailing_slash = false; } /* Loop over url parts and clean up */ $first_part_seen = false; $path_components_clean = array(); while ($path_components) { $part = array_shift($path_components); assert('is_string($part)'); $part = str_strip_prefix($part, '/'); $part = str_strip_suffix($part, '/'); if (!strlen($part)) { continue; } /* Use url encoding, but the first path component may be something * like "http://...", which should not be url encoded. */ if (!$first_part_seen && str_contains($part, '://')) { $first_part_seen = true; $part_encoded = $part; } else { $part_encoded = urlencode($part); /* Url decoding also escapes slashes and some other characters * we want to keep, since escaping those would disallow passing * path components like "/path/to/file". A slash cannot be used * in a filename anyway, so we special-case some characters. */ $part_encoded = str_replace(array('%2F', '%7E'), array('/', '~'), $part_encoded); } $path_components_clean[] = $part_encoded; } /* Build url by joining all cleaned parts, and adding a leading and * trailing slash, if appropriate. */ $url = join('/', $path_components_clean); if (strlen($url)) { /* Path is not empty */ if ($use_leading_slash) { $url = sprintf('/%s', $url); } if ($use_trailing_slash) { $url = sprintf('%s/', $url); } } elseif ($use_leading_slash || $use_trailing_slash) { /* Path is empty, a slash is required */ $url = '/'; } /* Query parameters */ assert('is_assoc_array($parameters)'); $parameters_escaped = array(); foreach ($parameters as $name => $value) { if (is_null($value)) { $parameters_escaped[] = urlencode($name); } else { assert('is_string($value);'); $parameters_escaped[] = sprintf('%s=%s', urlencode($name), urlencode($value)); } } if ($parameters_escaped) { $url = sprintf('%s?%s', $url, implode('&', $parameters_escaped)); } return $url; }