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 = 'Alias match value: \
+ \
+ Value to replace: \
+ \
+ ';
+ } else if (type == 'disable') {
+ title = 'New Disabled Route';
+ html = 'Disable match value: \
+ \
+ Match exact? \
+ \
+ ';
+ } else if (type == 'redirect') {
+ title = 'New Redirect Entry';
+ html = 'Redirect match value: \
+ \
+ Destination resource: \
+ \
+ ';
+ }
+ $.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
+ {% foreach Alias as match, action %}
+ {{match}} -> {{action}}
+
+
+
+
{% end %}
+
+
+
+ Add Entry
+ {% foreach Disable as match, action %}
+ {{match}} -> {% if action %}strict match{% else %}loose match{% end %}
+
+
+
+
{% end %}
+
+
+ Redirects (matches exact resource only)
+ Add Entry
+ {% foreach Redirect as match, action %}
+ {{match}} -> {{action}}
+
+
+
+
{% end %}
+
+
+
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.