Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit for the routes editor resource, #262

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ conf/app.*
conf/installed
conf/site.db
conf/config.php
conf/routes.*.php
install/cache/*
install/installed
lang/_index.php
1 change: 1 addition & 0 deletions apps/admin/conf/acl.php
Original file line number Diff line number Diff line change
Expand Up @@ -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"

; */ ?>
59 changes: 59 additions & 0 deletions apps/admin/css/routes.css
Original file line number Diff line number Diff line change
@@ -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; }
16 changes: 16 additions & 0 deletions apps/admin/handlers/routes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

$this->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);
83 changes: 83 additions & 0 deletions apps/admin/js/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
function forEach(arr,func){ var i = -1; while(++i < arr.length) func.bind(arr[i])(i, arr); };

function icons(){
return '<div class="actions">\
<i class="fa fa-lg fa-times clickable" title="Remove" onclick="if(confirm(\'Do you want to remove this route?\')){this.parentNode.parentNode.remove();}"></i>\
<i class="fa fa-lg fa-arrow-up clickable" title="Move Up" onclick="moveEntry(this.parentNode.parentNode, 1);"></i>\
<i class="fa fa-lg fa-arrow-down clickable" title="Move Down" onclick="moveEntry(this.parentNode.parentNode, 0);"></i>\
</div>';

}

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 = '<label for="match">Alias match value:</label><br>\
<input type="text" id="match" name="match" required autofocus><br>\
<label for="action">Value to replace:</label><br>\
<input type="text" id="action" name="action" required><br><br>\
<input type="submit" value="Add" onclick="addAlias(this.parentNode.querySelector(\'#match\').value,this.parentNode.querySelector(\'#action\').value);return false;">';
} else if (type == 'disable') {
title = 'New Disabled Route';
html = '<label for="match">Disable match value:</label><br>\
<input type="text" id="match" name="match" required autofocus><br>\
<label for="action">Match exact?</label>\
<input type="checkbox" id="action" name="action"><br><br>\
<input type="submit" value="Add" onclick="addDisable(this.parentNode.querySelector(\'#match\').value,this.parentNode.querySelector(\'#action\').checked);return false;">';
} else if (type == 'redirect') {
title = 'New Redirect Entry';
html = '<label for="match">Redirect match value:</label><br>\
<input type="text" id="match" name="match" required autofocus><br>\
<label for="action">Destination resource:</label>\
<input type="text" id="action" name="action" required><br><br>\
<input type="submit" value="Add" onclick="addRedirect(this.parentNode.querySelector(\'#match\').value,this.parentNode.querySelector(\'#action\').value);return false;">';
}
$.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 = '<li data-match="'+ match +'" data-action="'+ action +'">'+ match +'<br>-> '+ action + icons() +'</li>';
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 = '<li data-match="'+ match +'" data-action="'+ action +'">'+ match +'<br>-> '+ (action?'strict match':'loose match') + icons() +'</li>';
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 = '<li data-match="'+ match +'" data-action="'+ action +'">'+ match +'<br>-> '+ action + icons() +'</li>';
document.querySelector('#routes-redirect>ul').insertAdjacentHTML('afterBegin',html);
$.close_dialog();
}
16 changes: 16 additions & 0 deletions apps/admin/lib/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace admin;

use \Appconf;
use \Ini;

class API extends \Restful {

Expand All @@ -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.');
}
}
34 changes: 34 additions & 0 deletions apps/admin/views/routes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div id="routes-data">
<div class="save clickable" onclick="saveRoutes();">Save Routes <i class="fa fa-floppy-o"></i></div><br>
<section id="routes-alias">
<header>Aliases (matches from left only)</header>
<div class="new-entry clickable" onclick="newEntry('alias');">Add Entry</div>
<ul>{% foreach Alias as match, action %}
<li data-match="{{match}}" data-action="{{action}}">{{match}}<br>-> {{action}}<div class="actions">
<i class="fa fa-lg fa-times clickable" title="Remove" onclick="if(confirm('Do you want to remove this route?')){this.parentNode.parentNode.remove();}"></i>
<i class="fa fa-lg fa-arrow-up clickable" title="Move Up" onclick="moveEntry(this.parentNode.parentNode, 1);"></i>
<i class="fa fa-lg fa-arrow-down clickable" title="Move Down" onclick="moveEntry(this.parentNode.parentNode, 0);"></i>
</div></li>{% end %}</ul>
</section>
<section id="routes-disable">
<header>Disabled Routes</header>
<div class="new-entry clickable" onclick="newEntry('disable');">Add Entry</div>
<ul>{% foreach Disable as match, action %}
<li data-match="{{match}}" data-action="{{action}}">{{match}}<br>-> {% if action %}strict match{% else %}loose match{% end %}<div class="actions">
<i class="fa fa-lg fa-times clickable" title="Remove" onclick="if(confirm('Do you want to remove this route?')){this.parentNode.remove();}"></i>
<i class="fa fa-lg fa-arrow-up clickable" title="Move Up" onclick="moveEntry(this.parentNode, 'up');"></i>
<i class="fa fa-lg fa-arrow-down clickable" title="Move Down" onclick="moveEntry(this.parentNode, 'down');"></i>
</div></li>{% end %}</ul>
</section>
<section id="routes-redirect">
<header>Redirects (matches exact resource only)</header>
<div class="new-entry clickable" onclick="newEntry('redirect');">Add Entry</div>
<ul>{% foreach Redirect as match, action %}
<li data-match="{{match}}" data-action="{{action}}">{{match}}<br>-> {{action}}<div class="actions">
<i class="fa fa-lg fa-times clickable" title="Remove" onclick="if(confirm('Do you want to remove this route?')){this.parentNode.remove();}"></i>
<i class="fa fa-lg fa-arrow-up clickable" title="Move Up" onclick="moveEntry(this.parentNode, 'up');"></i>
<i class="fa fa-lg fa-arrow-down clickable" title="Move Down" onclick="moveEntry(this.parentNode, 'down');"></i>
</div></li>{% end %}</ul>
</section>
</div>
<br clear="both" />
12 changes: 10 additions & 2 deletions lib/FrontController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
||
Expand All @@ -186,17 +189,22 @@ 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;
}
}}
unset($_routes);
}
unset($_path);

/**
* Route the request to the appropriate handler and get
Expand Down
12 changes: 12 additions & 0 deletions lib/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down