From 078816befee9c98aa248f78360561af948c67ef9 Mon Sep 17 00:00:00 2001 From: secretr Date: Wed, 30 Nov 2011 15:14:02 +0000 Subject: [PATCH] Better routing/URL assembling, inline documentation, large number of fixes, news area improvements - should work flowless with every URL configuration. Awaiting testing results for more stability fixes. --- e107_admin/eurl.php | 12 +- e107_core/shortcodes/single/nextprev.php | 11 +- e107_core/url/news/e_url.php | 60 ++++++--- e107_core/url/news/rewrite/e_url.php | 31 +++-- e107_core/url/news/rewrite_extended/e_url.php | 102 +++++++++++---- e107_core/url/search/e_url.php | 7 +- e107_core/url/search/rewrite/e_url.php | 7 +- e107_core/url/system/rewrite/e_url.php | 16 ++- e107_core/url/user/e_url.php | 7 +- e107_core/url/user/rewrite/e_url.php | 7 +- e107_handlers/application.php | 120 ++++++++++++++---- news.php | 13 +- 12 files changed, 299 insertions(+), 94 deletions(-) diff --git a/e107_admin/eurl.php b/e107_admin/eurl.php index d9732a9fe..90381245d 100644 --- a/e107_admin/eurl.php +++ b/e107_admin/eurl.php @@ -89,11 +89,13 @@ class eurl_admin_ui extends e_admin_controller_ui { $labels = array(); $obj = eDispatcher::getConfigObject($module, $location); - if($obj) - { - $admin = $obj->admin(); - $labels = vartrue($admin['labels'], array()); - } + if(!$obj) continue; + $config = $obj->config(); + if(!$config || !vartrue($config['config']['allowMain'])) continue; + $admin = $obj->admin(); + $labels = vartrue($admin['labels'], array()); + + $this->prefs['url_main_module']['writeParms'][$module] = vartrue($section['name'], eHelper::labelize($module)); } diff --git a/e107_core/shortcodes/single/nextprev.php b/e107_core/shortcodes/single/nextprev.php index 5cbb707d3..91ac92b12 100644 --- a/e107_core/shortcodes/single/nextprev.php +++ b/e107_core/shortcodes/single/nextprev.php @@ -134,7 +134,16 @@ function nextprev_shortcode($parm = '') if($total_pages <= 1) { return ''; } // urldecoded once by parse_str() - $url = str_replace(array('--FROM--', '--AMP--'), array('[FROM]', '&'), $parm['url']); + if(substr($parm['url'], 0, 5) == 'url::') + { + // New - use URL assembling engine + // Format is: url::route::params::options + // Example: url::news/list/category::id=xxx&name=yyy&page=--PAGE--::full=1 + // WARNING - url parameter string have to be rawurlencode-ed BEFORE passed to the shortcode, or it'll break everything + $urlParms = explode('::', $parm['url']); + $url = str_replace(array('--FROM--', '--AMP--'), array('[FROM]', '&'), $e107->url->create($urlParms[1], $urlParms[2], varset($urlParms[3]))); + } + else $url = str_replace(array('--FROM--', '--AMP--'), array('[FROM]', '&'), $parm['url']); // Simple parser vars $e_vars = new e_vars(array( diff --git a/e107_core/url/news/e_url.php b/e107_core/url/news/e_url.php index 22cdaef23..12a1e3bad 100644 --- a/e107_core/url/news/e_url.php +++ b/e107_core/url/news/e_url.php @@ -1,7 +1,12 @@ array( + 'allowMain' => false, // [optional] default false; disallow this module (while using this config) to be set as site main URL namespace 'noSingleEntry' => true, // [optional] default false; disallow this module to be shown via single entry point when this config is used 'legacy' => '{e_BASE}news.php', // [optional] default empty; if it's a legacy module (no single entry point support) - URL to the entry point script 'format' => 'get', // get|path - notify core for the current URL format, if set to 'get' rules will be ignored 'selfParse' => true, // [optional] default false; use only this->parse() method, no core routine URL parsing 'selfCreate' => true, // [optional] default false; use only this->create() method, no core routine URL creating - 'defaultRoute' => '', // [optional] default empty; route (no leading module) used when module is found with no additional controller/action information e.g. /news/ + 'defaultRoute' => 'list/new',// [optional] default empty; route (no leading module) used when module is found with no additional controller/action information e.g. /news/ 'errorRoute' => '', // [optional] default empty; route (no leading module) used when module is found but no inner route is matched, leave empty to force error 404 page - 'urlSuffix' => '', // [optional] default empty; string to append to the URL (e.g. .html) + 'urlSuffix' => '', // [optional] default empty; string to append to the URL (e.g. .html), not used when format is 'get' or legacy non-empty ), - 'rules' => array() // rule set array + 'rules' => array(), // rule set array - can't be used with format 'get' and noSingleEntry true + + ### [optional] vars mapping (create URL routine), override per rule is allowed + ### Keys of this array will be used as a map for finding values from the provided parameters array. + ### Those values will be assigned to new keys - corresponding values of mapVars array + ### It gives extremely flexibility when used with allowVars. For example we pass $news item array as + ### it's retrieved from the DB, with no modifications. This gives us the freedom to create any variations of news + ### URLs using the DB data with a single line URL rule. Another aspect of this feature is the simplified code + ### for URL assembling - we just do eRouter::create($theRoute, $newsDbArray) + ### Not used when in selfCreate mod (create url) + 'mapVars' => array( + //'news_id' => 'id', + //'news_sef' => 'name', + ), + + ### [optional] allowed vars definition (create URL routine), override per rule is allowed + ### This numerical array serves as a filter for passed vars when creating URLs + ### Everything outside this scope is ignored while assembling URLs. Exception are route variables. + ### For example: when is present in the route string, there is no need to extra allow 'id' + ### To disallow everything but route variables, set allowVars to false + ### When format is get, false value will disallow everything (no params) and default preserved variables + ### will be extracted from mapVars (if available) + ### Default value is empty array + ### Not used when in selfCreate mod (create url) + 'allowVars' => array(/*'page', 'name'*/), + + ### Those are regex templates, allowing us to avoid the repeating regex patterns writing in your rules. + ### varTemplates are merged with the core predefined templates. Full list with core regex templates and examples can be found + ### in rewrite_extended news URL config + 'varTemplates' => array(/*'testIt' => '[\d]+'*/), ); } @@ -87,20 +122,9 @@ class core_news_url extends eUrlConfig case 'day': case 'month': case 'year': - $url .= $route[1].'-'.$params['id']; - break; - - case 'nextprev': - $route = $params['route']; - unset($params['route']); - if($route != 'list/nextprev') - { - $params['page'] = '[FROM]'; - $url = $this->create($route, $params); - unset($tmp); - } - break; - + if($page) $page = '.'.$page; + $url .= $route[1].'.'.$params['id'].$page; + break; default: $url = 'news.php'; diff --git a/e107_core/url/news/rewrite/e_url.php b/e107_core/url/news/rewrite/e_url.php index 66a670a16..68f51d598 100644 --- a/e107_core/url/news/rewrite/e_url.php +++ b/e107_core/url/news/rewrite/e_url.php @@ -1,7 +1,10 @@ array( + 'allowMain' => true, 'noSingleEntry' => false, // [optional] default false; disallow this module to be shown via single entry point when this config is used 'legacy' => '{e_BASE}news.php', // [optional] default empty; if it's a legacy module (no single entry point support) - URL to the entry point script 'format' => 'path', // get|path - notify core for the current URL format, if set to 'get' rules will be ignored 'selfParse' => true, // [optional] default false; use only this->parse() method, no core routine URL parsing 'selfCreate' => true, // [optional] default false; use only this->create() method, no core routine URL creating 'defaultRoute' => 'list/items', // [optional] default empty; route (no leading module) used when module is found with no additional controller/action information e.g. /news/ - 'errorRoute' => '', // [optional] default empty; route (no leading module) used when module is found but no inner route is matched, leave empty to force error 404 page - 'urlSuffix' => '', // [optional] default empty; string to append to the URL (e.g. .html) + 'urlSuffix' => '.html', // [optional] default empty; string to append to the URL (e.g. .html) ), - - 'rules' => array() // rule set array ); } @@ -42,7 +43,7 @@ class core_news_rewrite_url extends eUrlConfig if('--FROM--' != $params['page']) $page = $params['page'] ? intval($params['page']) : '0'; else $page = '--FROM--'; - if(!$route) $route = 'item/default'; + if(!$route) $route = 'list/items'; if(is_string($route)) $route = explode('/', $route, 2); $r = array(); @@ -88,7 +89,7 @@ class core_news_rewrite_url extends eUrlConfig // news/Category/Category-Name?page=xxx // news/Short/Category-Name?page=xxx $r[0] = $route[1] == 'category' ? 'Short' : 'Category'; - $r[1] = $params['id']; + $r[1] = $params['name'] ? $params['name'] : $params['id']; if($page) $parm = array('page' => $page); } break; @@ -96,7 +97,8 @@ class core_news_rewrite_url extends eUrlConfig case 'day': case 'month': case 'year': - $r[0] = $route[1].'-'.$params['id']; + $r = array($route[1], intval($params['id'])); + if($page) $parm = array('page' => $page); break; default: @@ -179,7 +181,8 @@ class core_news_rewrite_url extends eUrlConfig if(!vartrue($parts[1])) $id = 0; else $id = intval($parts[1]); - $this->legacyQueryString = 'day-'.$id; + $this->legacyQueryString = 'day.'.$id.'.'.$page; + return 'list/day'; break; # could be pref or LAN constant @@ -187,15 +190,17 @@ class core_news_rewrite_url extends eUrlConfig if(!vartrue($parts[1])) $id = 0; else $id = intval($parts[1]); - $this->legacyQueryString = 'month-'.$id; + $this->legacyQueryString = 'month.'.$id.'.'.$page; + return 'list/month'; break; - # could be pref or LAN constant + # could be pref or LAN constant - not supported yet case 'year': if(!vartrue($parts[1])) $id = 0; else $id = intval($parts[1]); - $this->legacyQueryString = 'year-'.$id; + $this->legacyQueryString = 'year.'.$id.'.'.$page; + //return 'list/year'; break; # force not found diff --git a/e107_core/url/news/rewrite_extended/e_url.php b/e107_core/url/news/rewrite_extended/e_url.php index 196ef80d8..ab5607fd2 100644 --- a/e107_core/url/news/rewrite_extended/e_url.php +++ b/e107_core/url/news/rewrite_extended/e_url.php @@ -1,7 +1,13 @@ '{e_BASE}news.php', // [optional] default empty; if it's a legacy module (no single entry point support) - URL to the entry point script; override per rule is allowed 'format' => 'path', // get|path - notify core for the current URL format, if set to 'get' rules will be ignored 'defaultRoute' => 'list/items', // [optional] default empty; route (no leading module) used when module is found with no additional controller/action information e.g. /news/ - 'legacyQuery' => '', // [optional] default null; default legacy query string template, parsed (simpleParse) with requestParams values (request object) and GET vars part of allowVars array (rule); override per rule is allowed + 'urlSuffix' => '', + 'allowMain' => true, ### default vars mapping (create URL), override per rule is allowed 'mapVars' => array( @@ -24,6 +31,28 @@ class core_news_rewrite_extended_url extends eUrlConfig ### false means - disallow all vars beside those required by the rules ### Override per rule is allowed 'allowVars' => false, + + ### Best news - you don't need to write one and the same + ### regex over and over again. Even better news - you might avoid + ### writing regex at all! Just use the core regex templates, they + ### should fit almost every case. + ### Here is a test custom regex template: + 'varTemplates' => array('testIt' => '[\d]+'), + + /* Predefined Core regex templates, see usage below + 'az' => '[A-Za-z]+', // NOTE - it won't match non-latin word characters! + 'alphanum' => '[\w\pL]+', + 'sefsecure' => '[\w\pL.\-\s!,]+', + 'secure' => '[^\/\'"\\<%]+', + 'number' => '[\d]+', + 'username' => '[\w\pL.\-\s!,]+', + 'azOptional' => '[A-Za-z]{0,}', + 'alphanumOptional' => '[\w\pL]{0,}', + 'sefsecureOptional' => '[\w\pL.\-\s!,]{0,}', + 'secureOptional' => '[^\/\'"\\<%]{0,}', + 'numberOptional' => '[\d]{0,}', + 'usernameOptional' => '[\w\pL.\-\s!,]{0,}', + */ ), 'rules' => array( @@ -31,36 +60,41 @@ class core_news_rewrite_extended_url extends eUrlConfig '' => array('list/items', 'allowVars' => array('page'), 'legacyQuery' => 'default.0.{page}', ), 'Category' => array('list/items', 'allowVars' => array('page'), 'legacyQuery' => 'default.0.{page}', ), - ## URL with ID and Title - no DB call, balanced performance! - 'Category//' => array('list/items', 'allowVars' => array('page'), 'mapVars' => array('category_id' => 'id', 'category_title' => 'name'), 'legacyQuery' => 'list.{id}.{page}'), + ## URL with ID and Title - no DB call, balanced performance, name optional + ## Demonstrating the usage of custom user defined regex template defined above - 'testIt' + 'Category//' => array('list/category', 'allowVars' => array('page'), 'mapVars' => array('category_id' => 'id', 'category_name' => 'name'), 'legacyQuery' => 'list.{id}.{page}'), - ## URL with ID only - best performance! - // 'Category/' => array('list/items', 'allowVars' => array('page'), 'legacyQuery' => 'list.{id}.{page}', 'mapVars' => array('category_id' => 'id')), - ## URL with Title only - prettiest and slowest! - ##'Category/' => array('list/items', 'allowVars' => array('page'), 'mapVars' => array('category_title' => 'name'), 'legacyQuery' => 'list.{id}.{page}', 'parseCallback' => 'categoryIdByTitle'), + ## URL with Title only - prettiest and slowest! Example with direct regex - no templates + //'Category/' => array('list/category', 'allowVars' => array('page'), 'mapVars' => array('category_name' => 'name'), 'legacyQuery' => 'list.{name}.{page}', 'parseCallback' => 'categoryIdByTitle'), + ## URL with ID only - best performance, fallback when no sef name provided + 'Category/' => array('list/category', 'allowVars' => array('page'), 'legacyQuery' => 'list.{id}.{page}', 'mapVars' => array('category_id' => 'id')), + ### View item requested by id or string, if you remove the catch ALL example, uncomment at least on row from this block ### leading category name example - could be enabled together with the next example to handle creating of URLs without knowing the category title // 'View//' => array('view/item', 'mapVars' => array('news_title' => 'name', 'category_name' => 'category'), 'legacyQuery' => 'extend.{name}', 'parseCallback' => 'itemIdByTitle'), // to be noted here - value 'name' is replaced by item id within the callback method; TODO replace news_title with news_sef field - // 'View/' => array('view/item', 'mapVars' => array('news_title' => 'name'), 'legacyQuery' => 'extend.{name}', 'parseCallback' => 'itemIdByTitle'), - // 'View/' => array('view/item', 'mapVars' => array('news_id' => 'id'), 'legacyQuery' => 'extend.{id}'), + // 'View/' => array('view/item', 'mapVars' => array('news_title' => 'name', 'news_id' => 'id'), 'legacyQuery' => 'extend.{name}', 'parseCallback' => 'itemIdByTitle'), + // 'View/' => array('view/item', 'mapVars' => array('news_id' => 'id'), 'legacyQuery' => 'extend.{id}'), ## URL with ID and Title - no DB call, balanced performance! - 'Short//' => array('list/short', 'allowVars' => array('page'), 'mapVars' => array('category_id' => 'id', 'category_title' => 'name'), 'legacyQuery' => 'list.{id}.{page}'), + 'Short//' => array('list/short', 'allowVars' => array('page'), 'mapVars' => array('category_id' => 'id', 'category_name' => 'name'), 'legacyQuery' => 'cat.{id}.{page}'), + ## fallback when name is not provided + 'Short/' => array('list/short', 'allowVars' => array('page'), 'mapVars' => array('category_id' => 'id'), 'legacyQuery' => 'cat.{id}.{page}'), // less used after - 'Brief/' => array('list/short', 'allowVars' => array('page'), 'legacyQuery' => 'cat.{id}.{page}', 'mapVars' => array('news_id' => 'id')), - 'Day/' => array('list/day', 'legacyQuery' => 'day-{id}'), - 'Month/' => array('list/month', 'legacyQuery' => 'month-{id}'), - 'Year/' => array('list/year', 'legacyQuery' => 'year-{id}'), + //'Brief/' => array('list/short', 'allowVars' => array('page'), 'legacyQuery' => 'cat.{id}.{page}', 'mapVars' => array('category_id' => 'id')), + 'Day/' => array('list/day', 'allowVars' => array('page'), 'legacyQuery' => 'day.{id}.{page}'), + 'Month/' => array('list/month', 'allowVars' => array('page'), 'legacyQuery' => 'month.{id}.{page}'), + //'Year/' => array('list/year', 'allowVars' => array('page'), 'legacyQuery' => 'year.{id}.{page}'), not supported yet ### View news item - kinda catch all - very bad performance when News is chosen as default namespace - two additional DB queries on every site call! - // Leading category name - uncomment to enable - '/' => array('view/item', 'mapVars' => array('news_title' => 'name', 'category_name' => 'category'), 'legacyQuery' => 'extend.{name}', 'parseCallback' => 'itemIdByTitle'), - // Base location as item view - uncomment to enable - // '' => array('view/item', 'mapVars' => array('news_title' => 'name'), 'legacyQuery' => 'extend.{name}', 'parseCallback' => 'itemIdByTitle'), - + ## Leading category name - uncomment to enable + '/' => array('view/item', 'mapVars' => array('news_title' => 'name', 'mapVars' => array('category_name' => 'category', 'news_title' => 'name', 'news_id' => 'id'), 'category_name' => 'category'), 'legacyQuery' => 'extend.{name}', 'parseCallback' => 'itemIdByTitle'), + // Base location as item view - fallback if category sef is missing + '' => array('view/item', 'mapVars' => array('news_title' => 'name'), 'mapVars' => array('news_id' => 'id', 'news_title' => 'name'), 'legacyQuery' => 'extend.{name}', 'parseCallback' => 'itemIdByTitle'), + // fallback if news sef is missing + 'View/' => array('view/item', 'mapVars' => array('news_id' => 'id'), 'legacyQuery' => 'extend.{id}'), ) ); @@ -109,15 +143,25 @@ class core_news_rewrite_extended_url extends eUrlConfig public function itemIdByTitle(eRequest $request) { $name = $request->getRequestParam('name'); - if(!$name || is_numeric($name)) return; + if(($id = $request->getRequestParam('id'))) + { + $request->setRequestParam('name', $id); + return; + } + elseif(!$name) return; + elseif(is_numeric($name)) + { + return; + } $sql = e107::getDb('url'); $name = e107::getParser()->toDB($name); - if($sql->db_Select('news', 'news_id', "news_title='{$name}'")) // TODO - it'll be news_url (new) field + if($sql->db_Select('news', 'news_id', "news_title='{$name}'")) // TODO - it'll be news_sef (new) field { $name = $sql->db_Fetch(); $request->setRequestParam('name', $name['news_id']); } + else $request->setRequestParam('name', 0); } /** @@ -127,14 +171,24 @@ class core_news_rewrite_extended_url extends eUrlConfig public function categoryIdByTitle(eRequest $request) { $name = $request->getRequestParam('name'); - if(!$name || is_numeric($name)) return; + if(($id = $request->getRequestParam('id'))) + { + $request->setRequestParam('name', $id); + return; + } + elseif(!$name) return; + elseif(is_numeric($name)) + { + return; + } $sql = e107::getDb('url'); $id = e107::getParser()->toDB($name); - if($sql->db_Select('news_category', 'category_id', "category_name='{$name}'")) // TODO - it'll be category_url (new) field + if($sql->db_Select('news_category', 'category_id', "category_name='{$name}'")) // TODO - it'll be category_sef (new) field { $name = $sql->db_Fetch(); $request->setRequestParam('name', $name['category_id']); } + else $request->setRequestParam('name', 0); } } \ No newline at end of file diff --git a/e107_core/url/search/e_url.php b/e107_core/url/search/e_url.php index cdc12e77a..931b5ce99 100644 --- a/e107_core/url/search/e_url.php +++ b/e107_core/url/search/e_url.php @@ -1,5 +1,10 @@ array( - 'format' => 'path', // get|path - notify core for the current URL format, if set to 'get' rules will be ignored - 'defaultRoute' => 'error/notfound', // [optional] default empty; route (no leading module) used when module is found with no additional controller/action information e.g. /news/ + 'allowMain' => true, + 'format' => 'path', + 'defaultRoute' => 'error/notfound', + 'errorRoute' => 'error/notfound', ), // rule set array 'rules' => array( - 'error404' => 'error/notfound', + 'error404' => 'error/notfound', + 'hello' => 'error/hello-world', ) ); } diff --git a/e107_core/url/user/e_url.php b/e107_core/url/user/e_url.php index aa46951f9..80c04094b 100644 --- a/e107_core/url/user/e_url.php +++ b/e107_core/url/user/e_url.php @@ -1,5 +1,10 @@ setMainModule(e107::getPref('url_main_module', '')); $this->_loadConfig() ->setAliases(); + // we need config first as setter does some checks if module can be set as main + $this->setMainModule(e107::getPref('url_main_module', '')); } /** @@ -736,14 +737,14 @@ class eRouter */ public function setMainModule($module) { - if(!$module) return $this; - + if(!$module || !$this->isModule($module) || !$this->getConfigValue($module, 'allowMain')) return $this; $this->_mainNsModule = $module; return $this; } /** * Get main url namespace module + * @return string */ public function getMainModule() { @@ -752,6 +753,8 @@ class eRouter /** * Check if given module is the main module + * @param string $module + * @return boolean */ public function isMainModule($module) { @@ -1169,6 +1172,16 @@ class eRouter return isset($this->_globalConfig[$module]) ? $this->_globalConfig[$module] : array(); } + /** + * Retrieve single value from a module global configuration array + * @param string $module system module + * @return array configuration + */ + public function getConfigValue($module, $key, $default = null) + { + return isset($this->_globalConfig[$module]) && isset($this->_globalConfig[$module][$key]) ? $this->_globalConfig[$module][$key] : $default; + } + /** * Get system name of a module by its alias * Returns null if $alias is not an existing alias @@ -1462,7 +1475,7 @@ class eRouter if(vartrue($config['selfParse'])) { // controller/action[/additional/parms] - + if(vartrue($config['urlSuffix'])) $rawPathInfo = $this->removeUrlSuffix($rawPathInfo, $config['urlSuffix']); $route = $this->configCallback($module, 'parse', array($rawPathInfo, $_GET, $request, $this, $config), $config['location']); } // default module route @@ -1495,12 +1508,12 @@ class eRouter $vars->action = $request->getAction(); if($rule->allowVars) { - foreach ($rule->allowVars as $key => $value) + foreach ($rule->allowVars as $key) { if(isset($_GET[$key]) && !$request->isRequestParam($key)) { // sanitize - $vars->$key = preg_replace('/[^\d\w]/', '', $_GET[$key]); + $vars->$key = preg_replace('/[^\d\w\-]/', '', $_GET[$key]); } } } @@ -1722,9 +1735,15 @@ class eRouter $format = isset($config['format']) && $config['format'] ? $config['format'] : self::FORMAT_GET; + $urlSuffix = ''; + // Fix base url for legacy links if($config['noSingleEntry']) $base = $options['full'] ? SITEURL : e_HTTP; - + elseif(self::FORMAT_GET !== $config['format']) + { + $urlSuffix = $this->urlSuffix; + if(isset($config['urlSuffix'])) $urlSuffix = $config['urlSuffix']; + } // TODO - main module - don't include it in the return URL // Create by config callback @@ -1756,12 +1775,11 @@ class eRouter } if($params) { - $params = $this->createPathInfo($params, $options); - return $base.implode('/', $route).'?'.$params.$anc; + return $base.implode('/', $route).$urlSuffix.'?'.$params.$anc; } - - return $base.implode('/', $route).$anc; + if(!$route) $urlSuffix = ''; + return $base.implode('/', $route).$urlSuffix.$anc; } @@ -1798,19 +1816,25 @@ class eRouter } } - if($config['allowVars']) + // false means - no vars are allowed, nothing to preserve here + if($config['allowVars'] === false) $params = array(); + // default empty array value - try to guess what's allowed - mapVars is the best possible candidate + elseif(empty($config['allowVars']) && !empty($config['mapVars'])) $params = array_unique(array_values($config['mapVars'])); + // disallow everything but valid URL parameters + if(!empty($config['allowVars'])) { $copy = $params; $params = array(); foreach ($config['allowVars'] as $key) { - $params[$key] = $copy[$key]; + if(isset($copy[$key])) $params[$key] = $copy[$key]; } unset($copy); } if($format === self::FORMAT_GET) { + $urlSuffix = ''; $copy = $params; $params = array(); $params[$this->routeVar] = implode('/', $route); @@ -1822,10 +1846,11 @@ class eRouter $route = array(); } $params = $this->createPathInfo($params, $options); - return $base.implode('/', $route).'?'.$params.$anc; + if(!$route) $urlSuffix = ''; + return $base.implode('/', $route).$urlSuffix.'?'.$params.$anc; } - - return $format === self::FORMAT_GET ? $base.'?'.$this->routeVar.'='.implode('/', $route).$anc : $base.implode('/', $route).$anc; + if(!$route) $urlSuffix = ''; + return $format === self::FORMAT_GET ? $base.'?'.$this->routeVar.'='.implode('/', $route).$anc : $base.implode('/', $route).$urlSuffix.$anc; } /** @@ -1873,7 +1898,7 @@ class eRouter /** * Parses a path info into URL segments * Be sure to not use non-unique chars for equal and ampersand signs, or you'll break your URLs - * + * XXX - maybe we can switch to http_build_query(), should be able to do everything we need in a much better way * @param eRequest $request * @param string $pathInfo path info * @param string $equal @@ -2021,6 +2046,39 @@ class eUrlRule * @var string */ public $legacyQuery; + + /** + * Core regex templates + * Example usage - route will result in + * @var array + */ + public $regexTemplates = array( + 'az' => '[A-Za-z]+', // NOTE - it won't match non-latin word characters! + 'alphanum' => '[\w\pL]+', + 'sefsecure' => '[\w\pL.\-\s!,]+', + 'secure' => '[^\/\'"\\<%]+', + 'number' => '[\d]+', + 'username' => '[\w\pL.\-\s!,]+', // TODO - should equal to username pattern, sync it + 'azOptional' => '[A-Za-z]{0,}', + 'alphanumOptional' => '[\w\pL]{0,}', + 'sefsecureOptional' => '[\w\pL.\-\s!,]{0,}', + 'secureOptional' => '[^\/\'"\\<%]{0,}', + 'numberOptional' => '[\d]{0,}', + 'usernameOptional' => '[\w\pL.\-\s!,]{0,}', // TODO - should equal to username pattern, sync it + ); + + /** + * User defined regex templates + * @var array + */ + public $varTemplates = array(); + + /** + * All regex templates + * @var e_var + */ + protected $_regexTemplates; + /** * Constructor. @@ -2034,6 +2092,7 @@ class eUrlRule if ($fromCache && !$pattern) { $this->setData($route); + $this->_regexTemplates = new e_vars($this->regexTemplates); return; } @@ -2052,13 +2111,26 @@ class eUrlRule { foreach ($matches2[1] as $name) $this->references[$name] = "<$name>"; } + + if($this->varTemplates) + { + // don't override core regex templates + $this->regexTemplates = array_merge($this->varTemplates, $this->regexTemplates); + $this->varTemplates = array(); + } + $this->_regexTemplates = new e_vars($this->regexTemplates); if (preg_match_all('/<(\w+):?(.*?)?>/', $pattern, $matches)) { $tokens = array_combine($matches[1], $matches[2]); + $tp = e107::getParser(); foreach ($tokens as $name => $value) { if ($value === '') $value = '[^\/]+'; + elseif($value[0] == '{') + { + $value = $tp->simpleParse($value, $this->_regexTemplates, '[^\/]+'); + } $tr["<$name>"] = "(?P<$name>$value)"; if (isset($this->references[$name])) $tr2["<$name>"] = $tr["<$name>"]; else $this->params[$name] = $value; @@ -2127,6 +2199,7 @@ class eUrlRule else return false; } + // map vars first foreach ($this->mapVars as $srcKey => $dstKey) { if (isset($params[$srcKey])/* && !isset($params[$dstKey])*/) @@ -2136,17 +2209,19 @@ class eUrlRule } } - // disallow everything but valid URL parameters + // false means - no vars are allowed, preserve only route vars if($this->allowVars === false) $this->allowVars = array_keys($this->params); + // empty array (default) - everything is allowed - if($this->allowVars) + // disallow everything but valid URL parameters + if(!empty($this->allowVars)) { $copy = $params; $params = array(); - $this->allowVars = array_merge($this->allowVars, array_keys($this->params)); + $this->allowVars = array_unique(array_merge($this->allowVars, array_keys($this->params))); foreach ($this->allowVars as $key) { - $params[$key] = $copy[$key]; + if(isset($copy[$key])) $params[$key] = $copy[$key]; } unset($copy); } @@ -2161,7 +2236,7 @@ class eUrlRule } foreach ($this->params as $key => $value) if (!isset($params[$key])) return false; - + foreach ($this->params as $key => $value) { $tr["<$key>"] = $params[$key]; @@ -2225,7 +2300,6 @@ class eUrlRule { $manager->parsePathInfo($request, ltrim(substr($pathInfo, strlen($matches[0])), '/')); } - return (null !== $this->routePattern ? strtr($this->route, $tr) : $this->route); } else return false; diff --git a/news.php b/news.php index 52452762f..a63ef23e2 100644 --- a/news.php +++ b/news.php @@ -105,6 +105,12 @@ $nobody_regexp = "'(^|,)(".str_replace(",", "|", e_UC_NOBODY).")(,|$)'"; $newsRoute = 'list/short'; break; + case 'day': + case 'month': + $newsUrlparms['id'] = $sub_action; + $newsRoute = 'list/'.$action; + break; + default: $newsRoute = 'list/items'; break; @@ -680,9 +686,10 @@ else $param['current_action'] = $action; // NEW - news category title when in list - if($sub_action && vartrue($newsAr[1]['category_name'])) + if($sub_action && 'list' == $action && vartrue($newsAr[1]['category_name'])) { - $category_name = $newsAr[1]['category_name']; + // we know category name - pass it to the nexprev url + $category_name = $newsUrlparms['name'] = $newsAr[1]['category_name']; if(!isset($NEWSLISTCATTITLE)) { $NEWSLISTCATTITLE = "

".$tp->toHTML($category_name,FALSE,'TITLE')."

"; @@ -717,6 +724,8 @@ else $amount = ITEMVIEW; $nitems = defined('NEWS_NEXTPREV_NAVCOUNT') ? '&navcount='.NEWS_NEXTPREV_NAVCOUNT : '' ; $url = rawurlencode(e107::getUrl()->create($newsRoute, $newsUrlparms)); + // Example of passing route data instead building the URL outside the shortcode - for a reference only + // $url = rawurlencode('url::'.$newsRoute.'::'.http_build_query($newsUrlparms, null, '&')); $parms = 'tmpl_prefix='.deftrue('NEWS_NEXTPREV_TMPL', 'default').'&total='.$news_total.'&amount='.$amount.'¤t='.$newsfrom.$nitems.'&url='.$url; echo $tp->parseTemplate("{NEXTPREV={$parms}}");