public function createURL($route_name, $variables = [])
    {
        // Make sure the route configuration has been loaded
        if (!isset($this->routes)) {
            $this->routes = Core::i()->config->getConfiguration('routes');
        }
        // Make sure the named route exists
        if (!isset($this->routes[$route_name])) {
            throw new \Exception('The specified route, ' . $route_name . ', was not defined.');
        }
        // Merge the default values and the passed variables into an array
        $variables = array_merge($this->routes[$route_name]['defaults'], $variables);
        $query_string = [];
        $url = str_replace('[', '', $this->routes[$route_name]['route']);
        $url = str_replace(']', '', $url);
        foreach ($variables as $key => $value) {
            if (false !== strpos($url, ':' . $key . ':')) {
                // The variable is in the url, replace it
                $url = str_replace(':' . $key . ':', $value, $url);
            } elseif ($key != 'controller' && $key != 'action' && $key != 'module') {
                // The variable is not in the url, add it to the query string as long as it is not the controller or action
                $query_string[] = urlencode($key) . '=' . urlencode($value);
            }
        }
        // If there is a trailing '/index' remove it. There can be up to three (module, controller, action)
        $url = rtrim($url, '/index');
        $url = rtrim($url, '/index');
        $url = rtrim($url, '/index');
        // Add the query string
        if (count($query_string) > 0) {
            $url .= '?' . implode('&', $query_string);
        }
        $url = ltrim($url, '/');
        // If a locale was specified, add that to the beginning of the url
        if (\Framework::i()->locale_set) {
            $url = $this->getLinkLocale(\Framework::i()->locale) . '/' . $url;
        }
        return $url;
    }