From 6cf1f90711a8e2e4c95f93b7c959ce752ea8c546 Mon Sep 17 00:00:00 2001 From: secretr Date: Tue, 6 Dec 2011 16:01:34 +0000 Subject: [PATCH] URL config refactoring --- e107_admin/eurl.php | 8 +- e107_core/url/news/rewrite_extended_url.php | 194 ++++++++++++++ e107_core/url/news/rewrite_url.php | 277 ++++++++++++++++++++ e107_core/url/news/{e_url.php => url.php} | 11 +- e107_core/url/search/rewrite_url.php | 47 ++++ e107_core/url/search/{e_url.php => url.php} | 0 e107_core/url/system/rewrite_url.php | 49 ++++ e107_core/url/system/{e_url.php => url.php} | 0 e107_core/url/user/rewrite_url.php | 98 +++++++ e107_core/url/user/{e_url.php => url.php} | 0 e107_handlers/application.php | 164 +++++++++--- 11 files changed, 801 insertions(+), 47 deletions(-) create mode 100644 e107_core/url/news/rewrite_extended_url.php create mode 100644 e107_core/url/news/rewrite_url.php rename e107_core/url/news/{e_url.php => url.php} (95%) create mode 100644 e107_core/url/search/rewrite_url.php rename e107_core/url/search/{e_url.php => url.php} (100%) create mode 100644 e107_core/url/system/rewrite_url.php rename e107_core/url/system/{e_url.php => url.php} (100%) create mode 100644 e107_core/url/user/rewrite_url.php rename e107_core/url/user/{e_url.php => url.php} (100%) diff --git a/e107_admin/eurl.php b/e107_admin/eurl.php index e425eba43..0f145789e 100644 --- a/e107_admin/eurl.php +++ b/e107_admin/eurl.php @@ -199,7 +199,7 @@ class eurl_admin_ui extends e_admin_controller_ui $this->addTitle(LAN_EURL_NAME_CONFIG); $active = e107::getPref('url_config'); //echo(e107::getUrl()->create('system/error/notfound', '', 'full=1')); - + //eRouter::adminReadConfigs(e_CORE.'url/news', 'core'); $set = array(); // all available URL modules $set['url_modules'] = eRouter::adminReadModules(); @@ -398,7 +398,7 @@ class eurl_admin_form_ui extends e_admin_form_ui $checked = varset($obj->current[$module]) == $location ? ' checked="checked"' : ''; $path = eDispatcher::getConfigPath($module, $location, false); - if(!is_readable($path)) $path = str_replace('/e_url.php', '/', $tp->replaceConstants(eDispatcher::getConfigPath($module, $location, true), true)).' ('.LAN_EURL_LOCATION_NONE.')'; + if(!is_readable($path)) $path = str_replace('/url.php', '/', $tp->replaceConstants(eDispatcher::getConfigPath($module, $location, true), true)).' ('.LAN_EURL_LOCATION_NONE.')'; else $path = $tp->replaceConstants(eDispatcher::getConfigPath($module, $location, true), true); $label = vartrue($section['label'], $index == 0 ? LAN_EURL_DEFAULT : eHelper::labelize(ltrim(strstr($location, '/'), '/'))); @@ -527,7 +527,7 @@ class eurl_admin_form_ui extends e_admin_form_ui $help = array(); $admin = $obj->config->admin(); $lan = $lanDef[0]; - $url = e107::getUrl()->create($module, '', array('full' => 1)); + $url = e107::getUrl()->create($module, '', array('full' => 1, 'encode' => 0)); $defVal = isset($currentAliases[$lan]) && in_array($module, $currentAliases[$lan]) ? array_search($module, $currentAliases[$lan]) : $module; $section = vartrue($admin['labels'], array()); @@ -553,7 +553,7 @@ class eurl_admin_form_ui extends e_admin_form_ui foreach ($lans as $code => $lan) { - $url = e107::getUrl()->create($module, '', array('lan' => $code, 'full' => 1)); + $url = e107::getUrl()->create($module, '', array('lan' => $code, 'full' => 1, 'encode' => 0)); $defVal = isset($currentAliases[$code]) && in_array($module, $currentAliases[$code]) ? array_search($module, $currentAliases[$code]) : $module; $text .= "
"; $text .= $this->text('eurl_aliases['.$code.']['.$module.']', $defVal).' ['.$lan.']'.$this->help(LAN_EURL_FORM_HELP_ALIAS_1.' '.$lan.''); diff --git a/e107_core/url/news/rewrite_extended_url.php b/e107_core/url/news/rewrite_extended_url.php new file mode 100644 index 000000000..ab5607fd2 --- /dev/null +++ b/e107_core/url/news/rewrite_extended_url.php @@ -0,0 +1,194 @@ + array( + '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; 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/ + 'urlSuffix' => '', + 'allowMain' => true, + + ### default vars mapping (create URL), override per rule is allowed + 'mapVars' => array( + 'news_id' => 'id', + 'news_title' => 'name', + ), + + ### Numerical array containing allowed vars by default (create URL, used for legacyQuery parsing in parse routine as well), + ### 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( + ### simple matches first - PERFORMANCE + '' => 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, 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 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', '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_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('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', '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}'), + + ) + ); + } + + /** + * Query mapping in format route?params: + * - item/vew?id=xxx -> ?extend.id + * - list/items[?page=xxx] -> default.0.page + * - list/category?id=xxx[&page=xxx] -> list.id.page + * - list/category?id=0[&page=xxx] -> default.0.page + * - list/short?id=xxx[&page=xxx] -> cat.id.page + * - list/day?id=xxx -> ?day-id + * - list/month?id=xxx -> ?month-id + * - list/year?id=xxx -> ?year-id + * - list/nextprev?route=xxx -> PARSED_ROUTE.[FROM] (recursive parse() call) + */ + + + /** + * Admin callback + * Language file not loaded as all language data is inside the lan_eurl.php (loaded by default on administration URL page) + */ + public function admin() + { + // static may be used for performance + static $admin = array( + 'labels' => array( + 'name' => LAN_EURL_CORE_NEWS, // Module name + 'label' => LAN_EURL_NEWS_REWRITEX_LABEL, // Current profile name + 'description' => LAN_EURL_NEWS_REWRITEX_DESCR, // + ), + 'form' => array(), // Under construction - additional configuration options + 'callbacks' => array(), // Under construction - could be used for e.g. URL generator functionallity + ); + + return $admin; + } + + ### CUSTOM METHODS ### + + /** + * view/item by name callback + * @param eRequest $request + */ + public function itemIdByTitle(eRequest $request) + { + $name = $request->getRequestParam('name'); + 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_sef (new) field + { + $name = $sql->db_Fetch(); + $request->setRequestParam('name', $name['news_id']); + } + else $request->setRequestParam('name', 0); + } + + /** + * list/items by name callback + * @param eRequest $request + */ + public function categoryIdByTitle(eRequest $request) + { + $name = $request->getRequestParam('name'); + 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_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/news/rewrite_url.php b/e107_core/url/news/rewrite_url.php new file mode 100644 index 000000000..6bcb8e8c7 --- /dev/null +++ b/e107_core/url/news/rewrite_url.php @@ -0,0 +1,277 @@ + 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/ + 'urlSuffix' => '.html', // [optional] default empty; string to append to the URL (e.g. .html) + ), + ); + } + + /** + * When returning array, module or it's corresponding alias will be prefixed + * Create link so that it can be mapped by the parse() method + * - view/item?id=xxx -> news/xxx + * - list/items[?page=xxx] -> news[?page=xxx] + * - list/category?id=xxx[&page=xxx] -> news/Category/xxx?page=xxx + * - list/category?id=0[&page=xxx] -> news?page=xxx + * - list/short?id=xxx[&page=xxx] -> news/Short/xxx?page=xxx + * - list/category?id=xxx[&page=xxx] -> news?page=xxx + * - list/day?id=xxx -> news/Day-id + * - list/month?id=xxx -> news/Month-id + * - list/year?id=xxx -> news/Year-id + * - list/nextprev?route=xxx -> PARSED_ROUTE?page=[FROM] (recursive parse() call) + */ + public function create($route, $params = array(), $options = array()) + { + + if('--FROM--' != vartrue($params['page'])) $page = varset($params['page']) ? intval($params['page']) : '0'; + else $page = '--FROM--'; + + if(!$route) $route = 'list/items'; + if(is_string($route)) $route = explode('/', $route, 2); + if('index' == $route[0]) + { + $route[0] = 'list'; + $route[1] = 'items'; + } + elseif('index' == $route[1]) + { + $route[1] = 'items'; + } + + $r = array(); + $parm = array(); + + if($route[0] == 'view') + { + ## news are passing array as it is retrieved from the DB, map vars to proper values + if(isset($params['news_id']) && !empty($params['news_id'])) $params['id'] = $params['news_id']; + if(isset($params['news_title']) && !empty($params['news_title'])) $params['id'] = $params['news_title']; // TODO - news_sef + + switch ($route[1]) + { + case 'item': + $r[0] = $params['id']; // news/ID + break; + + default: + + break; + } + } + elseif($route[0] == 'list') + { + ## news are passing array as it is retrieved from the DB, map vars to proper values + if(isset($params['category_id']) && !empty($params['category_id'])) $params['id'] = $params['category_id']; + if(isset($params['category_name']) && !empty($params['category_name'])) $params['name'] = $params['category_name']; // TODO - news_sef + + switch ($route[1]) + { + + case 'items': + $r[0] = ''; + if($page) $parm = array('page' => $page); // news?page=xxx + break; + + case 'category': + case 'short': + if(!vartrue($params['id'])) + { + $r[0] = ''; + if($page) $parm = array('page' => $page); // news?page=xxx + } + else + { + // news/Category/Category-Name?page=xxx + // news/Short/Category-Name?page=xxx + $r[0] = $route[1] == 'category' ? 'Category' : 'Short'; + $r[1] = $params['name'] ? $params['name'] : $params['id']; + if($page) $parm = array('page' => $page); + } + break; + + case 'day': + case 'month': + case 'year': + $r = array($route[1], intval($params['id'])); + if($page) $parm = array('page' => $page); + break; + + default: + + break; + } + } + + if(empty($r)) return false; + + return array($r, $parm); + } + + /** + * Manually parse request + * Pathinfo DOESN'T contain leading 'module' (e.g news or alias 'Blog') + * Retruned route shouldn't contain module as well, unless you manipulate $request directly and set $request->routed to true + * Mapped URLs: + * - news/News-Item -> extend.xxx + * - news/Category/Category-Name?page=10 -> list.xxx.10 + * - news/Day|Month-xxx -> day|month-xxx + */ + public function parse($pathInfo, $params, $request, $router, $config) + { + $page = $params['page'] ? intval($params['page']) : '0'; + if(!$pathInfo) + { + ## this var is used by default from legacy() method + ## you may override legacy() method + ## Keep in mind legacy() is not triggered at all if parse() returns false or $request->routed is set to true + $this->legacyQueryString = $page ? 'default.0.'.$page : ''; + return $config['defaultRoute']; + } + + ## no controller/action pair - news item view - map to extend.xxx + if(strpos($pathInfo, '/') === false) + { + $route = 'view/item'; + $id = is_numeric($pathInfo) ? intval($pathInfo) : $this->itemIdByTitle($pathInfo); + if(!$id) + { + ## let news.php handle missing news item + $this->legacyQueryString = 'extend.0'; + return $route; + } + $this->legacyQueryString = 'extend.'.$id; + return $route; + } + + $parts = explode('/', $pathInfo, 2); + + switch (strtolower($parts[0])) + { + # map to list.xxx.xxx + case 'short': + case 'category': + # Hardcoded leading string for categories, could be pref or LAN constant + if(!vartrue($parts[1])) + { + ## force not found as we don't want to have duplicated content (default.0.xxx) + return false; + } + else + { + if(!is_numeric($parts[1])) $id = $this->categoryIdByTitle($parts[1]); + else $id = intval($parts[1]); + } + if(!$id) + { + # let news.php handle it + $id = 0; + } + $action = $parts[0] == 'short' ? 'cat' : 'list'; + $this->legacyQueryString = $action.'.'.$id.'.'.$page; + return 'item/list'; + break; + + # could be pref or LAN constant + case 'day': + if(!vartrue($parts[1])) $id = 0; + else $id = intval($parts[1]); + + $this->legacyQueryString = 'day.'.$id.'.'.$page; + return 'list/day'; + break; + + # could be pref or LAN constant + case 'month': + if(!vartrue($parts[1])) $id = 0; + else $id = intval($parts[1]); + + $this->legacyQueryString = 'month.'.$id.'.'.$page; + return 'list/month'; + break; + + # 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.'.'.$page; + //return 'list/year'; + break; + + # force not found + default: + return false; + break; + } + + return false; + } + + /** + * Admin callback + * Language file not loaded as all language data is inside the lan_eurl.php (loaded by default on administration URL page) + */ + public function admin() + { + // static may be used for performance + static $admin = array( + 'labels' => array( + 'name' => LAN_EURL_CORE_NEWS, // Module name + 'label' => LAN_EURL_NEWS_REWRITE_LABEL, // Current profile name + 'description' => LAN_EURL_NEWS_REWRITE_DESCR, // + ), + 'form' => array(), // Under construction - additional configuration options + 'callbacks' => array(), // Under construction - could be used for e.g. URL generator functionallity + ); + + return $admin; + } + + ### CUSTOM METHODS ### + + //retrieve news_id by Title (XXX - news_sef column, equals to news_title if not set explicit) + public function itemIdByTitle($id) + { + $sql = e107::getDb('url'); + $tp = e107::getParser(); + $id = $tp->toDB($id); + if($sql->db_Select('news', 'news_id', "news_title='{$id}'")) // TODO - it'll be news_url (new) field + { + $id = $sql->db_Fetch(); + return $id['news_id']; + } + return false; + } + + //retrieve category_id by Title (XXX - category_sef column, equals to category_name if not set explicit) + public function categoryIdByTitle($id) + { + $sql = e107::getDb('url'); + $tp = e107::getParser(); + $id = $tp->toDB($id); + if($sql->db_Select('news_category', 'category_id', "category_name='{$id}'")) // TODO - it'll be category_url (new) field + { + $id = $sql->db_Fetch(); + return $id['category_id']; + } + return false; + } +} diff --git a/e107_core/url/news/e_url.php b/e107_core/url/news/url.php similarity index 95% rename from e107_core/url/news/e_url.php rename to e107_core/url/news/url.php index b8081a99f..b4f0de30b 100644 --- a/e107_core/url/news/e_url.php +++ b/e107_core/url/news/url.php @@ -75,8 +75,17 @@ class core_news_url extends eUrlConfig { if(!$params) return 'news.php'; + if(!$route) $route = 'list/items'; if(is_string($route)) $route = explode('/', $route, 2); - if(!varset($route[1])) $route[1] = ''; + if('index' == $route[0]) + { + $route[0] = 'list'; + $route[1] = 'items'; + } + elseif('index' == $route[1]) + { + $route[1] = 'items'; + } ## news are passing array as it is retrieved from the DB, map vars to proper values if(isset($params['news_id']) && !empty($params['news_id'])) $params['id'] = $params['news_id']; diff --git a/e107_core/url/search/rewrite_url.php b/e107_core/url/search/rewrite_url.php new file mode 100644 index 000000000..fbda39063 --- /dev/null +++ b/e107_core/url/search/rewrite_url.php @@ -0,0 +1,47 @@ + array( + 'legacy' => '{e_BASE}search.php', // [optional] default empty; if it's a legacy module (no single entry point support) - URL to the entry point script to be included + 'format' => 'path', // get|path - notify core for the current URL format, if set to 'get' rules will be ignored + 'defaultRoute' => 'index/index', // [optional] default empty; route (no leading module) used when module is found with no additional controller/action information e.g. /news/ + + ), + + // rule set array + 'rules' => array( + '/' => array('index/index', 'defaultVars' => array('id' => 0)), + ) + ); + } + + /** + * Admin callback + * Language file not loaded as all language data is inside the lan_eurl.php (loaded by default on administration URL page) + */ + public function admin() + { + // static may be used for performance + static $admin = array( + 'labels' => array( + 'name' => LAN_EURL_CORE_SEARCH, // Module name + 'label' => LAN_EURL_SEARCH_REWRITE_LABEL, // Current profile name + 'description' => LAN_EURL_SEARCH_REWRITE_DESCR, // + ), + 'form' => array(), // Under construction - additional configuration options + 'callbacks' => array(), // Under construction - could be used for e.g. URL generator functionallity + ); + + return $admin; + } +} diff --git a/e107_core/url/search/e_url.php b/e107_core/url/search/url.php similarity index 100% rename from e107_core/url/search/e_url.php rename to e107_core/url/search/url.php diff --git a/e107_core/url/system/rewrite_url.php b/e107_core/url/system/rewrite_url.php new file mode 100644 index 000000000..df9e44111 --- /dev/null +++ b/e107_core/url/system/rewrite_url.php @@ -0,0 +1,49 @@ + array( + 'allowMain' => true, + 'format' => 'path', + 'defaultRoute' => 'error/notfound', + 'errorRoute' => 'error/notfound', + + ), + + // rule set array + 'rules' => array( + 'error404' => 'error/notfound', + 'hello' => 'error/hello-world', + ) + ); + } + + /** + * Admin callback + * Language file not loaded as all language data is inside the lan_eurl.php (loaded by default on administration URL page) + */ + public function admin() + { + // static may be used for performance + static $admin = array( + 'labels' => array( + 'name' => LAN_EURL_CORE_SYSTEM, // Module name + 'label' => LAN_EURL_SYSTEM_REWRITE_LABEL, // Current profile name + 'description' => LAN_EURL_SYSTEM_REWRITE_DESCR, // + ), + 'form' => array(), // Under construction - additional configuration options + 'callbacks' => array(), // Under construction - could be used for e.g. URL generator functionallity + ); + + return $admin; + } +} diff --git a/e107_core/url/system/e_url.php b/e107_core/url/system/url.php similarity index 100% rename from e107_core/url/system/e_url.php rename to e107_core/url/system/url.php diff --git a/e107_core/url/user/rewrite_url.php b/e107_core/url/user/rewrite_url.php new file mode 100644 index 000000000..7262b8426 --- /dev/null +++ b/e107_core/url/user/rewrite_url.php @@ -0,0 +1,98 @@ + array( + 'noSingleEntry' => false, // [optional] default false; disallow this module to be shown via single entry point when this config is used + 'legacy' => '{e_BASE}user.php', // [optional] default empty; if it's a legacy module (no single entry point support) - URL to the entry point script to be included + 'format' => 'path', // get|path - notify core for the current URL format, if set to 'get' rules will be ignored + 'selfParse' => false, // [optional] default false; use only this->parse() method, no core routine URL parsing + 'selfCreate' => false, // [optional] default false; use only this->create() method, no core routine URL creating + 'defaultRoute' => 'myprofile/view', // [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) + 'mapVars' => array( // vars mapping (create URL) + 'user_id' => 'id', + 'user_name' => 'name', + ), + 'allowVars' => false, // allowed vars (create URL, used for legacyQuery parsing in parse routine as well), false means - disallow all vars beside those required by the rules + 'legacyQuery' => '' // default legacy query string template, null to disable, override possible by rule + ), + + // rule set array + 'rules' => array( + // simple matches first - PERFORMANCE + '' => array('myprofile/view', 'defaultVars' => array('id' => 0)), + 'Settings' => array('myprofile/edit', 'defaultVars' => array('id' => 0), 'legacy' => '{e_BASE}usersettings.php'), + 'List' => array('profile/list', 'allowVars' => array('page'), 'legacyQuery' => '{page}'), + 'Login' => array('login/index', 'legacy' => '{e_BASE}login.php'), + 'Register' => array('register/index', 'legacy' => '{e_BASE}signup.php'), + + // Regex involved next + //'' => array('profile/view', 'legacyQuery' => 'id.{id}'), + 'Edit/' => array('profile/edit', 'legacy' => '{e_BASE}usersettings.php', 'legacyQuery' => '{id}'), + + // Named requests - important to be in the end in this order! + 'Edit/' => array('profile/edit', 'legacy' => '{e_BASE}usersettings.php', 'legacyQuery' => '{id}', 'parseCallback' => 'idByName'), + // Last one - close to catch all! + '' => array('profile/view', 'legacyQuery' => 'id.{id}', 'parseCallback' => 'idByName'), + ) + ); + } + + /** + * Admin callback + * Language file not loaded as all language data is inside the lan_eurl.php (loaded by default on administration URL page) + */ + public function admin() + { + // static may be used for performance + static $admin = array( + 'labels' => array( + 'name' => LAN_EURL_CORE_USER, // Module name + 'label' => LAN_EURL_USER_REWRITE_LABEL, // Current profile name + 'description' => LAN_EURL_USER_REWRITE_DESCR, // + ), + 'form' => array(), // Under construction - additional configuration options + 'callbacks' => array(), // Under construction - could be used for e.g. URL generator functionallity + ); + + return $admin; + } + + ### CUSTOM METHODS ### + + /** + * profile/edit & profile/view callback + * @param eRequest $request + */ + public function idByName(eRequest $request) + { + $name = $request->getRequestParam('name'); + if(!$name) return; + + // if id only is passed, don't do DB query + if(is_numeric($name)) + { + $request->setRequestParam('id', $name)->setRequestParam('name', null); + return; + } + + $sql = e107::getDb('url'); + $name = e107::getParser()->toDB($name); + if($sql->db_Select('user', 'user_id', "user_name='{$name}'")) // XXX - new user_sef field? Discuss. + { + $name = $sql->db_Fetch(); + $request->setRequestParam('id', $name['user_id']); + } + } +} diff --git a/e107_core/url/user/e_url.php b/e107_core/url/user/url.php similarity index 100% rename from e107_core/url/user/e_url.php rename to e107_core/url/user/url.php diff --git a/e107_handlers/application.php b/e107_handlers/application.php index 23d073593..db7731a5f 100644 --- a/e107_handlers/application.php +++ b/e107_handlers/application.php @@ -326,7 +326,6 @@ class eDispatcher $moduleName = $request->getModuleName(); $className = $this->isDispatchable($request, false); - // dispatch based on rule settings if(!$className) { @@ -368,7 +367,7 @@ class eDispatcher $location = $tmp[0]; if(isset($tmp[1]) && !empty($tmp[1])) { - $custom = $tmp[1].'/'; + $custom = $tmp[1].'_'; } unset($tmp); if($module !== '*') $module .= '/'; @@ -376,20 +375,18 @@ class eDispatcher switch ($location) { case 'plugin': - if($custom) $custom = 'url/'.$custom; - return $sc ? '{e_PLUGIN}'.$module.$custom.'e_url.php' : e_PLUGIN.$module.$custom.'e_url.php'; + //if($custom) $custom = 'url/'.$custom; + return $sc ? '{e_PLUGIN}'.$module.'url/'.$custom.'url.php' : e_PLUGIN.$module.'url/'.$custom.'url.php'; break; - - //TODO - discuss + case 'core': if($module === '*') return $sc ? '{e_CORE}url/' : e_CORE.'url/'; - return $sc ? '{e_CORE}url/'.$module.$custom.'e_url.php' : e_CORE.'url/'.$module.$custom.'e_url.php'; + return $sc ? '{e_CORE}url/'.$module.$custom.'url.php' : e_CORE.'url/'.$module.$custom.'url.php'; break; - - // TODO - discuss + case 'override': if($module === '*') return $sc ? '{e_CORE}override/url/' : e_CORE.'override/url/' ; - return $sc ? '{e_CORE}override/url/'.$module.$custom.'url.php' : e_CORE.'override/url/'.$module.$custom.'e_url.php' ; + return $sc ? '{e_CORE}override/url/'.$module.$custom.'url.php' : e_CORE.'override/url/'.$module.$custom.'url.php' ; break; default: @@ -412,13 +409,11 @@ class eDispatcher case 'plugin': return $sc ? '{e_PLUGIN}'.$module.'/url/' : e_PLUGIN.$module.'/url/'; break; - - //TODO - discuss + case 'core': return $sc ? '{e_CORE}url/'.$module.'/' : e_CORE.'url/'.$module.'/'; break; - - // TODO - discuss + case 'override': return $sc ? '{e_CORE}override/url/'.$module.'/' : e_CORE.'override/url/'.$module.'/'; break; @@ -546,15 +541,23 @@ class eDispatcher */ public function isDispatchableModule($module, $controllerName, $location, $checkOverride = false) { - $path = self::getControllerPath($module, $controllerName, 'override', false); if($checkOverride || $location == 'override') { - $class_name = self::getControllerClass($module, $controllerName, $location); + $path = self::getControllerPath($module, $controllerName, 'override', false); + + $class_name = self::getControllerClass($module, $controllerName, 'override'); if($class_name && !class_exists($class_name, false) && is_readable($path)) include_once($path); if($class_name && class_exists($class_name, false)) return $class_name; } + // fallback to original dispatch location if any + if($location === 'override') + { + // check for real location + if(($location = eDispatcher::getModuleRealLocation($module)) === null) return false; + } + if($location !== 'override') { $path = self::getControllerPath($module, $controllerName, $location, false); @@ -578,24 +581,26 @@ class eDispatcher */ public function isDispatchable(eRequest $request, $checkReflection = false, $checkOverride = true) { - $location = self::getDispatchLocation($request->getModule()); + $location = self::getDispatchLocation($request->getModuleName()); $controllerName = $request->getControllerName(); $moduleName = $request->getModuleName(); + $className = false; - // dispatch based on rule settings + // dispatch based on url_config preference value, if config location is override and there is no + // override controller, additional check against real controller location will be made if($location) { $className = $this->isDispatchableModule($moduleName, $controllerName, $location, $checkOverride); } - else - { + //else + //{ # Disable plugin check for routes with no config info - prevent calling of non-installed plugins # We may allow this for plugins which don't have plugin.xml in the future // $className = $this->isDispatchableModule($moduleName, $controllerName, 'plugin', $checkOverride); // if(!$className) - $className = $this->isDispatchableModule($moduleName, $controllerName, 'core', $checkOverride); - } + //$className = $this->isDispatchableModule($moduleName, $controllerName, 'core', $checkOverride); + //} if(empty($className)) return false; elseif(!$checkReflection) return $className; @@ -642,7 +647,7 @@ class eDispatcher { if(null === $location) { - $location = self::getModuleLocation($module); + $location = self::getModuleConfigLocation($module); if(!$location) return null; } $reg = $module.'/'.$location; @@ -668,10 +673,9 @@ class eDispatcher * Auto discover module location from stored in core prefs data * @param string $module */ - public static function getModuleLocation($module) + public static function getModuleConfigLocation($module) { - // FIXME - based on url_module detection - real location, not override!!! - //retrieve from prefs + //retrieve from config prefs return e107::findPref('url_config/'.$module, ''); } @@ -682,7 +686,7 @@ class eDispatcher public static function getDispatchLocation($module) { //retrieve from prefs - $location = self::getModuleLocation($module); + $location = self::getModuleConfigLocation($module); if(!$location) return null; if(($pos = strpos($location, '/'))) //can't be 0 @@ -691,6 +695,27 @@ class eDispatcher } return $location; } + + + /** + * Auto discover module real location (and not currently set from url adminsitration) from stored in core prefs data + * @param string $module + */ + public static function getModuleRealLocation($module) + { + //retrieve from prefs + $searchArray = e107::findPref('url_modules'); + if(!$searchArray) return null; + + $search = array('core', 'plugin', 'override'); + + foreach ($search as $location) + { + $_searchArray = vartrue($searchArray[$location], array()); + if(in_array($module, $_searchArray)) return $location; + } + return null; + } } /** @@ -915,6 +940,45 @@ class eRouter return $config; } + /** + * Retrieve config array from a given system path + * @param string $path + * @param string $location core|plugin|override + */ + public static function adminReadConfigs($path, $location = null) + { + $file = e107::getFile(false); + $ret = array(); + + $file->mode = 'fname'; + $files = $file->setFileInfo('fname') + ->get_files($path, '^([a-z_]{1,}_)?url\.php$'); + + + foreach ($files as $file) + { + if(null === $location) + { + $c = eRouter::file2config($file, $location); + if($c) $ret[] = $c; + continue; + } + $ret[] = eRouter::file2config($file, $location); + } + return $ret; + } + + /** + * Convert filename to configuration string + * @param string $filename + * @param string $location core|plugin|override + */ + public static function file2config($filename, $location = '') + { + if($filename == 'url.php') return $location; + if($location) $location .= '/'; + return $location.substr($filename, 0, strrpos($filename, '_')); + } /** * Detect all available system url modules, used as a map on administration configuration path @@ -1109,8 +1173,9 @@ class eRouter $ret[$module] = array('core'); // read sub-locations - $path = eDispatcher::getConfigLocationPath($module, 'core'); - $sub = $fl->get_dirs($path); + $path = eDispatcher::getConfigLocationPath($module, 'core'); + //$sub = $fl->get_dirs($path); + $sub = eRouter::adminReadConfigs($path); if($sub) { @@ -1139,7 +1204,8 @@ class eRouter // read sub-locations $path = eDispatcher::getConfigLocationPath($module, 'plugin'); - $sub = $fl->get_dirs($path); + //$sub = $fl->get_dirs($path); + $sub = eRouter::adminReadConfigs($path); if($sub) { @@ -1173,7 +1239,8 @@ class eRouter // read sub-locations $path = eDispatcher::getConfigLocationPath($module, 'override'); - $sub = $fl->get_dirs($path); + //$sub = $fl->get_dirs($path); + $sub = eRouter::adminReadConfigs($path); if($sub) { @@ -1677,7 +1744,7 @@ class eRouter */ public function configCallback($module, $callBack, $params, $location) { - if(null == $location) $location = eDispatcher::getModuleLocation($module); + if(null == $location) $location = eDispatcher::getModuleConfigLocation($module); if(!$module || !($obj = eDispatcher::getConfigObject($module, $location))) return false; return call_user_func_array(array($obj, $callBack), $params); @@ -1770,6 +1837,8 @@ class eRouter $route[2] = 'index'; break; } + + # aliases $module = $route[0]; @@ -1777,6 +1846,7 @@ class eRouter $alias = $this->hasAlias($module, vartrue($options['lan'], null)) ? $this->getAliasFromModule($module, vartrue($options['lan'], null)) : $module; $route[0] = $alias; + if($options['encode']) $alias = rawurlencode($alias); $format = isset($config['format']) && $config['format'] ? $config['format'] : self::FORMAT_GET; @@ -1802,9 +1872,20 @@ class eRouter { $route = $tmp[0]; $params = $tmp[1]; - - if(!$this->isMainModule($module)) array_unshift($route, $alias); + if($options['encode']) $route = array_map('rawurlencode', $route); + $route = implode('/', $route); + + if(!$route) + { + $urlSuffix = ''; + if(!$this->isMainModule($module)) $route = $alias; + } + elseif (!$this->isMainModule($module)) + { + $route = $alias.'/'.$route; + } + } else { @@ -1815,11 +1896,10 @@ class eRouter if($format === self::FORMAT_GET) { - $params[$this->routeVar] = implode('/', $route); - $route = array(); + $params[$this->routeVar] = $route; + $route = ''; } - $route = implode('/', $route); - if(!$route || $route == $alias) $urlSuffix = ''; + if($params) { $params = $this->createPathInfo($params, $options); @@ -1836,7 +1916,7 @@ class eRouter { foreach ($rules as $rule) { - if (($url = $rule->createUrl($this, array($route[1], $route[2]), $params, $options)) !== false) return $base.($this->isMainModule($module) ? '' : $alias.'/').$url.$anc; + if (($url = $rule->createUrl($this, array($route[1], $route[2]), $params, $options)) !== false) return $base.rtrim(($this->isMainModule($module) ? '' : $alias.'/').$url, '/').$anc; } } @@ -2304,7 +2384,7 @@ class eUrlRule $suffix = $this->urlSuffix === null ? $manager->urlSuffix : $this->urlSuffix; $url = strtr($this->template, $tr); - + if (empty($params)) return $url !== '' ? $url.$suffix : $url; // apppend not supported, maybe in the future...? @@ -2317,7 +2397,7 @@ class eUrlRule $url .= '?'.$manager->createPathInfo($params, $options); } - return $url; + return rtrim($url, '/'); } /** @@ -2561,7 +2641,7 @@ class eController { if(method_exists($this, $actionMethodName)) { - // TODO request userParams() to store private data - check for noPopulat param here + // TODO request userParams() to store private data - check for noPopulate param here $request->populateRequestParams(); $this->$actionMethodName(); $this->postAction();