diff --git a/.gitignore b/.gitignore index ae8227610..88233406e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ conf/app.* conf/installed conf/site.db conf/config.php +conf/routes.*.php install/cache/* install/installed lang/_index.php diff --git a/apps/admin/conf/acl.php b/apps/admin/conf/acl.php index c293a5a55..a6ddc9228 100644 --- a/apps/admin/conf/acl.php +++ b/apps/admin/conf/acl.php @@ -7,6 +7,7 @@ admin/edit = "Update existing content" admin/delete = "Delete content" admin/toolbar = "Modify the admin toolbar" +admin/routes = "Modify config level routes" settings = "Modify site settings" ; */ ?> diff --git a/apps/admin/css/routes.css b/apps/admin/css/routes.css new file mode 100644 index 000000000..536ece094 --- /dev/null +++ b/apps/admin/css/routes.css @@ -0,0 +1,59 @@ +#routes-data { + width:100%; + position:relative; +} +#routes-data section { + float:left; + width:33%; + box-sizing:border-box; + border:black 1px dotted; + position:relative; + text-align:center +} + +#routes-data section header { + position:relative; + top:-1em; + padding:0 5px; + background-color:#f5f5f5; + display:inline-block; +} + +#routes-data .save { + position:absolute; + bottom:100%; + right:0; +} +#routes-data ul { + list-style:none; + margin:3px; +} +#routes-data li { + display:inline-block; + width: 100%; + margin-left: 0; + word-wrap:break-word; + text-align:left; + white-space:pre; + border-top:1px dashed; +} +#routes-data li:hover { background-color: rgba(147,39,143,0.15); } +.clickable { + cursor:pointer; + float:right; + line-height:1.1; +} +.clickable:hover { color:#abcdef; } +.actions { + float:right; + white-space:nowrap; +} +.new-entry { + width:100%; + height:0; + text-align:right; + margin:5px 5px 5px 0; + position:relative; + top:-1em; +} +.modal-dialog { min-height:0!important; } diff --git a/apps/admin/handlers/routes.php b/apps/admin/handlers/routes.php new file mode 100644 index 000000000..6942dbaed --- /dev/null +++ b/apps/admin/handlers/routes.php @@ -0,0 +1,16 @@ +require_acl('admin','admin/routes'); + +$page->title = __ ('Routes Editor'); +$page->layout = 'admin'; + +$this->run ('admin/util/modal'); +$this->run ('admin/util/notifier'); +$page->add_style ('/apps/admin/css/font-awesome/css/font-awesome.min.css'); +$page->add_style ('/apps/admin/js/routes.js'); +$page->add_style ('/apps/admin/css/routes.css'); +$path = conf_env_path('routes'); +if (!file_exists($path)) $path = 'conf/routes.php'; +$routes = Ini::parse($path,true); +echo $tpl->render ('admin/routes', $routes); \ No newline at end of file diff --git a/apps/admin/js/routes.js b/apps/admin/js/routes.js new file mode 100644 index 000000000..dbe035adc --- /dev/null +++ b/apps/admin/js/routes.js @@ -0,0 +1,83 @@ +function forEach(arr,func){ var i = -1; while(++i < arr.length) func.bind(arr[i])(i, arr); }; + +function icons(){ + return '
\ + \ + \ + \ +
'; + +} + +function saveRoutes(){ + var out = {Alias:{},Disable:{},Redirect:{}}; + var a = document.querySelector('#routes-alias>ul'), + d = document.querySelector('#routes-disable>ul'), + r = document.querySelector('#routes-redirect>ul'); + forEach(a.children,function(){out.Alias[this.dataset['match']] = this.dataset['action'];}); + forEach(d.children,function(){out.Disable[this.dataset['match']] = this.dataset['action'];}); + forEach(r.children,function(){out.Redirect[this.dataset['match']] = this.dataset['action'];}); + + $.post('/admin/api/routes',out).done(function(res){ + if (res.success) $.add_notice('Routes successfully saved.'); + else $.add_notice('Save unsuccessful: '+res.error); + }).fail(function(res){ $.add_notice('A connection or server error occured. Try again.'); }); +} + +function moveEntry(node, which){ + if (which == 1 && node.previousElementSibling){ node.previousElementSibling.insertAdjacentElement('beforeBegin',node); } + else if (which == 0 && node.nextElementSibling){ node.nextElementSibling.insertAdjacentElement('afterEnd',node); } +} + +function newEntry(type){ + var html, title; + if (type == 'alias') { + title = 'New Alias Entry'; + html = '
\ +
\ +
\ +

\ + '; + } else if (type == 'disable') { + title = 'New Disabled Route'; + html = '
\ +
\ + \ +

\ + '; + } else if (type == 'redirect') { + title = 'New Redirect Entry'; + html = '
\ +
\ + \ +

\ + '; + } + $.open_dialog(title,html,{width:200,height:200}); +} + +function addAlias(match,action){ + match = '/'+ match.replace(/^\/|\/$/g,''); + action = '/'+ action.replace(/^\/|\/$/g,''); + if (document.querySelectorAll('#routes-alias li[data-match="'+ match +'"]').length){ alert('An entry for '+ match +' already exists.'); return; } + var html = '
  • '+ match +'
    -> '+ action + icons() +'
  • '; + document.querySelector('#routes-alias>ul').insertAdjacentHTML('afterBegin',html); + $.close_dialog(); +} + +function addDisable(match,action){ + match = '/'+ match.replace(/^\/|\/$/g,''); + if (document.querySelectorAll('#routes-disable li[data-match="'+ match +'"]').length){ alert('An entry for '+ match +' already exists.'); return; } + var html = '
  • '+ match +'
    -> '+ (action?'strict match':'loose match') + icons() +'
  • '; + document.querySelector('#routes-disable>ul').insertAdjacentHTML('afterBegin',html); + $.close_dialog(); +} + +function addRedirect(match,action){ + match = '/'+ match.replace(/^\/|\/$/g,''); + action = '/'+ action.replace(/^\/|\/$/g,''); + if (document.querySelectorAll('#routes-redirect li[data-match="'+ match +'"]').length){ alert('An entry for '+ match +' already exists.'); return; } + var html = '
  • '+ match +'
    -> '+ action + icons() +'
  • '; + document.querySelector('#routes-redirect>ul').insertAdjacentHTML('afterBegin',html); + $.close_dialog(); +} diff --git a/apps/admin/lib/API.php b/apps/admin/lib/API.php index f231eda04..763fc2898 100644 --- a/apps/admin/lib/API.php +++ b/apps/admin/lib/API.php @@ -3,6 +3,7 @@ namespace admin; use \Appconf; +use \Ini; class API extends \Restful { @@ -24,4 +25,19 @@ public function post_toolbar () { } return Toolbar::save($out); } + + public function post_routes() { + $this->controller->require_acl('admin','admin/routes'); + $out = array('Alias'=>array(),'Disable'=>array(),'Redirect'=>array()); + foreach($_POST as $section => $group) { + if (isset($out[$section])) { + foreach($group as $match => $action) { + $out[$section][$match] = $action; + } + } + } + $path = conf_env_path('routes'); + if (Ini::write($out,$path)) return true; + else $this->error('Unable to write to config file.'); + } } diff --git a/apps/admin/views/routes.html b/apps/admin/views/routes.html new file mode 100644 index 000000000..2539a452d --- /dev/null +++ b/apps/admin/views/routes.html @@ -0,0 +1,34 @@ +
    +
    Save Routes

    +
    +
    Aliases (matches from left only)
    +
    Add Entry
    + +
    +
    +
    Disabled Routes
    +
    Add Entry
    + +
    +
    +
    Redirects (matches exact resource only)
    +
    Add Entry
    + +
    +
    +
    diff --git a/lib/FrontController.php b/lib/FrontController.php index ef6e38f36..8978f4e9f 100644 --- a/lib/FrontController.php +++ b/lib/FrontController.php @@ -166,10 +166,13 @@ function stripslashes_gpc (&$value) { /** * Run any config level route overrides. */ - if (file_exists ('conf/routes.php')) { - $_routes = parse_ini_file ('conf/routes.php',true); + $_path = conf_env_path('routes'); + if (!file_exists ($_path)) $_path = 'conf/routes.php'; + if (file_exists ($_path)) { + $_routes = parse_ini_file ($_path,true); if (isset($_routes['Disable'])){ foreach ($_routes['Disable'] as $_route => $_strict) { + $_route = '/'. trim($_route,'/'); // clean up leading/trailing slashes if ( (!$_strict && strpos($_SERVER['REQUEST_URI'],$_route) === 0 && $_SERVER['REQUEST_URI'] !== $_route) //match from left, exclude exact || @@ -186,10 +189,14 @@ function stripslashes_gpc (&$value) { }} if (isset($_routes['Redirect'])){ foreach ($_routes['Redirect'] as $_old => $_new) { + $_old = '/'. trim($_old,'/'); + $_new = '/'. trim($_new,'/'); if ($_old !== $_new && $_SERVER['REQUEST_URI'] == $_old) $controller->redirect($_new); }} if (isset($_routes['Alias'])){ foreach ($_routes['Alias'] as $_old => $_new) { + $_old = '/'. trim($_old,'/'); + $_new = '/'. trim($_new,'/'); if (strpos($_SERVER['REQUEST_URI'],$_old) === 0) { $_SERVER['REQUEST_URI'] = str_replace($_old,$_new,$_SERVER['REQUEST_URI']); break; @@ -197,6 +204,7 @@ function stripslashes_gpc (&$value) { }} unset($_routes); } + unset($_path); /** * Route the request to the appropriate handler and get diff --git a/lib/Functions.php b/lib/Functions.php index 2b34def18..c71d1a2a1 100644 --- a/lib/Functions.php +++ b/lib/Functions.php @@ -103,6 +103,18 @@ function conf ($section = false, $value = false, $update = null) { return @$conf; } +/** + * Checks against conditions to figure out the appropriate + * config file path to use. + */ + +function conf_env_path ($label) { + if (conf('Paths',$label) && conf('Paths',$label) !== 'conf/'. $label. '.php') + return conf('Paths',$label); + elseif (defined ('ELEFANT_ENV')/* && ELEFANT_ENV !== 'config'*/) + return 'conf/'. $label .'.'. ELEFANT_ENV .'.php'; + else return 'conf/'. $label.'.php'; +} /** * Implements a simple authentication mechanism based on callbacks. * You provide a verifier function and a communication method function.