commit 72118c0f9976e3d4a0d1017d987153fb7af80f89
Author: finwo <finwo@pm.me>
Date: Thu, 18 May 2017 12:24:05 +0200
git init
Diffstat:
17 files changed, 701 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+.idea
+*.bak
diff --git a/data/.gitignore b/data/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/docs/.htaccess b/docs/.htaccess
@@ -0,0 +1,4 @@
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteRule . not-found.php [NC,L]
diff --git a/docs/api/collections.php b/docs/api/collections.php
@@ -0,0 +1,9 @@
+<?php
+require_once 'init.php';
+$list = array_values(array_map(function($entry) {
+ return array('name'=>$entry);
+}, array_filter(scandir(APPROOT.DS.'data'), function($entry) {
+ return substr($entry,0,1) != '.';
+})));
+
+echo json_encode($list);
diff --git a/docs/api/data.php b/docs/api/data.php
@@ -0,0 +1,64 @@
+<?php
+require_once 'init.php';
+$method = $_SERVER['REQUEST_METHOD'];
+$params = url_params("/api/data/:collection/:id");
+if(is_null($params['collection'])) {
+ header('HTTP/1.0 400 Bad Request');
+ echo 'Bad Request - No collection given';
+ exit(0);
+}
+
+switch($method) {
+ case 'GET':
+ $dir = APPROOT.DS.'data'.DS.$params['collection'].DS;
+ if (!is_dir($dir)) {
+ header('HTTP/1.0 404 Not Found');
+ echo 'Not Found - Collection does not exist';
+ exit(0);
+ }
+
+ header('Content-Type: application/json');
+ if (!isset($params['id'])) print('[');
+ $dh = opendir($dir);
+ $sep = '';
+ while( $file = readdir($dh) ) {
+ if ( substr($file,0,1) == '.' ) continue;
+ $id = explode('.',$file);
+ $ext = array_pop($id);
+ $id = implode('.',$id);
+ if ( $ext != 'json' ) continue;
+ if ( isset($params['id']) ) {
+ if ( $params['id'] == $id ) {
+ $entity = json_decode(file_get_contents($dir.$file), true);
+ $entity['_id'] = $id;
+ print(json_encode($entity));
+ exit(0);
+ }
+ continue;
+ }
+ $entity = json_decode(file_get_contents($dir.$file), true);
+ $entity['_id'] = $id;
+ if ( isset($params['filter']) && !entity_matches($entity,$params['filter']) ) {
+ continue;
+ }
+ print($sep);
+ $sep = ',';
+ print(json_encode($entity));
+ }
+ if (!isset($params['id'])) print(']');
+ break;
+
+ case 'POST':
+ $dir = APPROOT.DS.'data'.DS.$params['collection'].DS;
+ if (!is_dir($dir)) {
+ mkdir($dir);
+ }
+
+ $id = uuid($params['collection']);
+ $file = $dir.$id.'.json';
+ file_put_contents($file,json_encode($_POST));
+ $_POST['_id'] = $id;
+ print(json_encode($_POST));
+
+ break;
+}
diff --git a/docs/api/init.php b/docs/api/init.php
@@ -0,0 +1,79 @@
+<?php
+
+require_once '..' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'autoload.php';
+
+function url_params( $template ) {
+ $data = $_GET;
+ $url = $_SERVER['REQUEST_URI'];
+ $url = explode('?',$url);
+ $url = explode('/',array_shift($url));
+ $template = explode('/',$template);
+ foreach ( $template as $index => $name ) {
+ if (substr($name,0,1)!=':') continue;
+ $name = substr($name,1);
+ $data[$name] = isset($url[$index]) ? $url[$index] : null;
+ }
+ return $data;
+}
+
+function get_deep( $data, $key = "" ) {
+ if (is_string($key)) $key = explode('.', $key);
+ if (!is_array($key)) return null;
+ if ( is_object($data) ) $data = (array) $data;
+ if ( !is_array($data) ) return null;
+ if ( count($key) == 1 ) {
+ $key = array_shift($key);
+ if ( isset($data[$key]) ) {
+ return $data[$key];
+ }
+ return null;
+ }
+ $current_key = array_shift($key);
+ return get_deep( $data[$current_key], $key );
+}
+
+function entity_matches( $entity, $filter ) {
+ foreach ( $filter as $key => $query ) {
+ $value = get_deep( $entity, $key );
+ switch(substr($query,0,1)) {
+ case '/':
+ if ( !preg_match($query, $value) ) return false;
+ break;
+ case '>':
+ $query = substr($query,1);
+ if ( !is_numeric($query) ) return false;
+ if ( !is_numeric($value) ) return false;
+ if (!(floatval($value) > floatval($query))) return false;
+ break;
+ case '<':
+ $query = substr($query,1);
+ if ( !is_numeric($query) ) return false;
+ if ( !is_numeric($value) ) return false;
+ if (!(floatval($value) < floatval($query))) return false;
+ break;
+ case '!':
+ $query = substr($query,1);
+ if ( $query == $value ) return false;
+ break;
+ default:
+ if ( $query != $value ) return false;
+ break;
+ }
+ }
+ return true;
+}
+
+function random_char() {
+ $alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ return $alphabet[rand(0, strlen($alphabet)-1)];
+}
+
+function uuid( $collection = null ) {
+ if (is_null($collection)) return uniqid('_');
+ $dir = APPROOT.DS.'data'.DS.$collection.DS;
+ if (!is_dir($dir)) return uniqid('_');
+ $output = '_';
+ while ( strlen($output) < 5 ) $output .= random_char();
+ while ( is_file($dir.$output.'.json') ) $output .= random_char();
+ return $output;
+}
diff --git a/docs/css/base.css b/docs/css/base.css
@@ -0,0 +1,82 @@
+@import url('https://fonts.googleapis.com/css?family=Oswald|Roboto');
+
+/* base */
+* {
+ -webkit-font-smoothing: subpixel-antialiased;
+ box-sizing : border-box;
+ line-height: 1em;
+ margin : 0;
+ padding : 0;
+}
+html {
+ color : #222;
+ font-family: 'Roboto', sans-serif;
+ font-size : calc( 1rem + 0.1vw );
+}
+h1, h2, h3, h4, h5, h6 {
+ font-family: 'Oswald', sans-serif;
+ padding-top : 1.00em;
+ padding-bottom: 0.50em;
+ margin-bottom : 0.25em;
+}
+h1 { font-size: 2.00rem; border-bottom: 1px solid #AAA; }
+h2 { font-size: 1.68rem; border-bottom: 1px solid #AAA; }
+h3 { font-size: 1.41rem; }
+h4 { font-size: 1.19rem; }
+h5 { font-size: 1.00rem; }
+h6 { font-size: 0.84rem; }
+.grid {
+ display : flex;
+ flex-flow: row wrap;
+ margin : 0 auto;
+}
+.grid > * {
+ flex-basis: 15rem;
+ flex-grow : 1;
+ margin : 0.5rem;
+}
+.padded {
+ padding: 0.5rem;
+}
+.padded > .title {
+ margin-top : 0.5rem;
+ padding-top: 0;
+}
+.shadow {
+ box-shadow: 0 0.05rem 0.15rem rgba(0,0,0,0.8);
+}
+a {
+ color : inherit;
+ text-decoration: none;
+}
+.container {
+ margin : 0 auto;
+ max-width: 50rem;
+}
+hr, p {
+ margin-bottom: 0.50rem;
+}
+ol, ul {
+ padding-left: 1.5em;
+}
+
+/* nav */
+nav {
+ background: #5AF;
+ color : #FFF;
+}
+nav a {
+ display: inline-block;
+ padding: 0.5rem;
+}
+nav a:hover {
+ background: #FFF;
+ color : #5AF;
+}
+.view,
+[data-source] {
+ display: none;
+}
+.view:target {
+ display: initial;
+}
diff --git a/docs/css/collections.css b/docs/css/collections.css
@@ -0,0 +1,19 @@
+ul.collections {
+ list-style-type : none;
+ padding : 0;
+}
+ul.collections li a {
+ display : inline-block;
+ padding : 0.5em;
+ width : 100%;
+}
+ul.collections li a:hover {
+ background-color: rgba(0,0,0,0.1);
+}
+ul.collections li a.active {
+ background: #5AF;
+ color : #FFF;
+}
+ul.collections li + li {
+ border-top: 1px solid rgba(0,0,0,0.2);
+}
diff --git a/docs/css/grid.css.php b/docs/css/grid.css.php
@@ -0,0 +1,41 @@
+<?php header('Content-Type: text/css'); ?>
+[class^=col-] { float:left;width:99.9% }
+@media all and (min-width: 720px) {
+<?php
+ function string_format( $template, $data, $prefix = "" ) {
+ foreach ($data as $key => $value) {
+ $compositeKey = $prefix . $key;
+ switch(gettype($value)) {
+ case 'string':
+ case 'double':
+ case 'float':
+ case 'integer':
+ $template = str_replace( '{'.$compositeKey.'}', $value, $template);
+ break;
+ case 'boolean':
+ $template = str_replace( '{'.$compositeKey.'}', $value ? 'true' : 'false', $template);
+ break;
+ case 'object':
+ $value = (array) $value;
+ case 'array':
+ $template = string_format( $template, $value, $compositeKey . '.');
+ break;
+ }
+ }
+ return $template;
+ }
+ function print_grid( $columns, $current = 1 ) {
+ if ( $current > $columns ) {return;}
+ print(string_format(" .col-{current}-{columns} {width:{width}%}\n",array(
+ 'columns'=>$columns,
+ 'current'=>$current,
+ 'width'=>$current/$columns*99.9
+ )));
+ print_grid($columns,$current+1);
+ }
+ $grid = array( 3, 5, 9, 12 );
+ foreach ($grid as $columns) {
+ print_grid($columns);
+ }
+?>
+}
diff --git a/docs/index.html b/docs/index.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Data Store</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="stylesheet" href="/css/base.css" />
+ <link rel="stylesheet" href="/css/grid.css" />
+ <link rel="stylesheet" href="/css/collections.css" />
+ </head>
+ <body>
+ <nav class="shadow">
+ <div class="container">
+ <a href="/">Data Store</a>
+ </div>
+ </nav>
+ <div class="container">
+ <div>
+ <div class="col-1-5 padded">
+ <ul class="shadow collections" data-source="/api/collections">
+ <li><a href="#{name}">{name}</a></li>
+ </ul>
+ </div>
+ <div class="col-4-5 padded">
+ WIP
+ </div>
+ </div>
+ </div>
+ <script src="/js/require.js"></script>
+ <script src="/js/data-source.js"></script>
+ </body>
+</html>
diff --git a/docs/js/ajax.js b/docs/js/ajax.js
@@ -0,0 +1,155 @@
+(function(exports) {
+
+ var factories = [
+ function () {return new XMLHttpRequest()},
+ function () {return new ActiveXObject("Msxml2.XMLHTTP")},
+ function () {return new ActiveXObject("Msxml3.XMLHTTP")},
+ function () {return new ActiveXObject("Microsoft.XMLHTTP")}
+ ];
+
+ function httpObject() {
+ var xmlhttp = false;
+ factories.forEach(function(factory) {
+ try {
+ xmlhttp = xmlhttp || factory();
+ } catch(e) {
+ return;
+ }
+ });
+ return xmlhttp;
+ }
+
+ function serializeObject(obj,prefix) {
+ var str = [], p;
+ for(p in obj) {
+ if (obj.hasOwnProperty(p)) {
+ var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
+ str.push((v !== null && typeof v === "object") ?
+ serializeObject(v, k) :
+ encodeURIComponent(k) + "=" + encodeURIComponent(v));
+ }
+ }
+ return str.join("&");
+ }
+
+ function SimplePromise()
+ {
+ var self = this,
+ queue = [],
+ doneFunction = null,
+ failFunction = function(e){throw e;},
+ started = false,
+ running = false;
+ this.then = function(callback) {
+ queue.push(callback);
+ if(started&&!running) self.run();
+ return self;
+ };
+ this.fail = function(callback) {
+ failFunction = callback;
+ if(started&&!running) self.run();
+ return self;
+ };
+ this.done = function(callback) {
+ doneFunction = callback;
+ if(started&&!running) self.run();
+ return self;
+ };
+ this.start = function(callback) {
+ queue.push(callback);
+ self.run();
+ return self;
+ };
+ this.run = function(data, done) {
+ started = true;
+ running = true;
+ var returnValue;
+ if(this!=self) {
+ done = this;
+ }
+ while(queue.length) {
+ var func = queue.shift();
+ if (!func) {
+ running = false;
+ if (typeof done === 'function') return done(data);
+ if (typeof doneFunction === 'function') return doneFunction(data);
+ return data;
+ }
+ try {
+ returnValue = null;
+ returnValue = func.call(null, data, self.run.bind(done), failFunction);
+ } catch(e) {
+ running = false;
+ if (typeof failFunction === 'function') return failFunction(e, data);
+ throw e;
+ }
+ if(!returnValue) {
+ return;
+ }
+ }
+ running = false;
+ if (typeof done === 'function') return done(data);
+ if (typeof doneFunction === 'function') return doneFunction(data);
+ return data;
+ };
+ }
+
+ function ajax( uri, options ) {
+ options = options || {};
+
+ var method = (options.method || 'GET').toUpperCase(),
+ data = options.data || {},
+ promise = new SimplePromise();
+
+ var req = httpObject();
+ if (!req) return;
+
+ // Insert data?
+ if(Object.keys(data).length) {
+ var serializedData = serializeObject(data);
+ switch(method) {
+ case 'GET':
+ uri += ((uri.indexOf('?')===false) ? '?' : '&') + serializedData;
+ data = {};
+ break;
+ case 'POST':
+ req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ break;
+ }
+ }
+
+ // Let's start
+ req.open(method, uri, true);
+
+ return promise.start(function(d, resolve, reject) {
+ req.onreadystatechange = function() {
+ if(req.readyState!=4) return;
+ if(req.status<200||req.status>=300) {
+ reject('Invalid response');
+ }
+ var receivedData = req.responseText;
+ try {
+ receivedData = JSON.parse(receivedData);
+ } catch(e) {
+ // Nothing to worry about
+ }
+ resolve(receivedData);
+ };
+ req.send(data);
+ });
+ }
+
+ // Export our freshly created plugin
+ exports.ajax = ajax;
+ if (typeof define === 'function' && define.amd) {
+ define('ajax', function() {
+ return ajax;
+ })
+ }
+
+ // Attach to window as well
+ if (typeof window !== 'undefined') {
+ window.ajax = ajax;
+ }
+
+})(typeof exports === 'object' && exports || this);
diff --git a/docs/js/data-source.js b/docs/js/data-source.js
@@ -0,0 +1,24 @@
+require('data-source', ['ajax', 'domchange'], function(ajax, onDomChange) {
+ function render( template, data ) {
+ if ( Array.isArray(data) ) {
+ return data.map(render.bind(null,template)).join('');
+ }
+ return template.format(data);
+ }
+ function process( element ) {
+ if(!element) {
+ document.querySelectorAll('[data-source]').forEach(process);
+ return;
+ }
+ var template = element.innerHTML,
+ url = element.getAttribute('data-source');
+ element.innerHTML = '';
+ element.removeAttribute('data-source');
+ ajax(url)
+ .then(function( data ) {
+ element.innerHTML = render(template,data);
+ })
+ }
+ onDomChange(process,10);
+ process();
+});
diff --git a/docs/js/domchange.js b/docs/js/domchange.js
@@ -0,0 +1,116 @@
+// See: http://stackoverflow.com/a/3219767
+(function (window) {
+ var last = +new Date();
+ var delay = 100; // default delay
+
+ // Manage event queue
+ var stack = [];
+
+ function callback() {
+ var now = +new Date();
+ if (now - last > delay) {
+ for (var i = 0; i < stack.length; i++) {
+ stack[i]();
+ }
+ last = now;
+ }
+ }
+
+ // Public interface
+ var onDomChange = function (fn, newdelay) {
+ if (newdelay) delay = newdelay;
+ stack.push(fn);
+ };
+
+ // Naive approach for compatibility
+ function naive() {
+
+ var last = document.getElementsByTagName('*');
+ var lastlen = last.length;
+ var timer = setTimeout(function check() {
+
+ // get current state of the document
+ var current = document.getElementsByTagName('*');
+ var len = current.length;
+
+ // if the length is different
+ // it's fairly obvious
+ if (len != lastlen) {
+ // just make sure the loop finishes early
+ last = [];
+ }
+
+ // go check every element in order
+ for (var i = 0; i < len; i++) {
+ if (current[i] !== last[i]) {
+ callback();
+ last = current;
+ lastlen = len;
+ break;
+ }
+ }
+
+ // over, and over, and over again
+ setTimeout(check, delay);
+
+ }, delay);
+ }
+
+ //
+ // Check for mutation events support
+ //
+
+ var support = {};
+
+ var el = document.documentElement;
+ var remain = 3;
+
+ // callback for the tests
+ function decide() {
+ if (support.DOMNodeInserted) {
+ window.addEventListener("DOMContentLoaded", function () {
+ if (support.DOMSubtreeModified) { // for FF 3+, Chrome
+ el.addEventListener('DOMSubtreeModified', callback, false);
+ } else { // for FF 2, Safari, Opera 9.6+
+ el.addEventListener('DOMNodeInserted', callback, false);
+ el.addEventListener('DOMNodeRemoved', callback, false);
+ }
+ }, false);
+ } else if (document.onpropertychange) { // for IE 5.5+
+ document.onpropertychange = callback;
+ } else { // fallback
+ naive();
+ }
+ }
+
+ // checks a particular event
+ function test(event) {
+ el.addEventListener(event, function fn() {
+ support[event] = true;
+ el.removeEventListener(event, fn, false);
+ if (--remain === 0) decide();
+ }, false);
+ }
+
+ // attach test events
+ if (window.addEventListener) {
+ test('DOMSubtreeModified');
+ test('DOMNodeInserted');
+ test('DOMNodeRemoved');
+ } else {
+ decide();
+ }
+
+ // do the dummy test
+ var dummy = document.createElement("div");
+ el.appendChild(dummy);
+ el.removeChild(dummy);
+
+ // expose
+ if (typeof define === 'function' && define.amd) {
+ define('domchange', function() {
+ return onDomChange;
+ })
+ }
+ window.onDomChange = onDomChange;
+})(window);
diff --git a/docs/js/require.js b/docs/js/require.js
@@ -0,0 +1,33 @@
+String.prototype.format = function(data) {
+ var output = this,
+ flatData = {};
+ (function flatten( obj, prefix ) {
+ prefix = prefix || '';
+ Object.keys(obj).forEach(function( key ) {
+ var compositeKey = prefix + key;
+ switch(typeof obj[key]) {
+ case 'string':
+ case 'number':
+ flatData[compositeKey] = obj[key];
+ break;
+ case 'object':
+ flatData[compositeKey] = obj[key];
+ flatten( obj[key], compositeKey + '.' );
+ break;
+ }
+ });
+ })(data);
+ Object.keys(flatData).forEach(function(key) {
+ while(output.indexOf('{'+key+'}')>=0) output = output.replace('{'+key+'}',flatData[key]);
+ });
+ return output;
+};
+window.require=window.define= (function() {
+ var a=function(o) {
+ return Object.keys(o).map(function(k){return o[k]});
+ },
+ q=[],
+ m={},
+ k=[],
+ l=function(n){if(k.indexOf(n)>=0)return;k.push(n);var x=new XMLHttpRequest();x.onreadystatechange=function(){if(x.readyState==XMLHttpRequest.DONE&&x.status==200)eval(x.responseText)};x.open('GET',r.uri+n+'.js',!0);x.send();},p=function(){var r=!0,ar=[],e=q.shift();if(!e)return;if(e.s&&m[e.s]){q.length&&setTimeout(p,5);return;}e.o.map(function(d){if(!r)return;if(m[d]){ar.push(m[d])}else{r=!1;q.push(e);l(d)}});if(r){e.s?m[e.s]=e.f.apply(null,ar):e.f.apply(null,ar)}q.length&&setTimeout(p,5)};function r(){var e={s:null,o:[],f:function(){}};a(arguments).map(function(arg){e[(typeof arg).substr(0,1)]=arg});q.push(e);p()}r.uri='/js/';r.amd=!0;return r
+})();
diff --git a/docs/not-found.php b/docs/not-found.php
@@ -0,0 +1,13 @@
+<?php
+include dirname(__DIR__).DIRECTORY_SEPARATOR.'src'.DIRECTORY_SEPARATOR.'autoload.php';
+$path = explode('/',$_SERVER['REQUEST_URI']);
+while(count($path)) {
+ if ( is_file(__DIR__.implode(DIRECTORY_SEPARATOR,$path).'.php') ) {
+ include(__DIR__.implode(DIRECTORY_SEPARATOR,$path).'.php');
+ exit(0);
+ }
+ array_pop($path);
+}
+
+header('HTTP/1.0 404 Not Found');
+echo 'Not Found';
diff --git a/docs/php_errors.log b/docs/php_errors.log
@@ -0,0 +1,13 @@
+[18-May-2017 10:46:24 Europe/Berlin] PHP Parse error: syntax error, unexpected ')', expecting variable (T_VARIABLE) or '$' in /var/www/vps/data-store/docs/css/grid.css.php on line 19
+[18-May-2017 10:50:15 Europe/Berlin] PHP Parse error: syntax error, unexpected '12' (T_LNUMBER), expecting ')' in /var/www/vps/data-store/docs/css/grid.css.php on line 30
+[18-May-2017 11:27:04 Europe/Berlin] PHP Parse error: syntax error, unexpected ')' in /var/www/vps/data-store/docs/api/data.php on line 4
+[18-May-2017 11:27:36 Europe/Berlin] PHP Warning: scandir(/var/www/vps/data-store/user): failed to open dir: No such file or directory in /var/www/vps/data-store/docs/api/data.php on line 4
+[18-May-2017 11:27:36 Europe/Berlin] PHP Warning: scandir(): (errno 2): No such file or directory in /var/www/vps/data-store/docs/api/data.php on line 4
+[18-May-2017 11:47:40 Europe/Berlin] PHP Parse error: syntax error, unexpected ')' in /var/www/vps/data-store/docs/api/data.php on line 4
+[18-May-2017 11:58:41 Europe/Berlin] PHP Notice: Undefined variable: entity in /var/www/vps/data-store/docs/api/data.php on line 29
+[18-May-2017 12:06:29 Europe/Berlin] PHP Notice: Undefined variable: contents in /var/www/vps/data-store/docs/api/data.php on line 36
+[18-May-2017 12:18:28 Europe/Berlin] PHP Fatal error: Call to undefined function random_int() in /var/www/vps/data-store/docs/api/init.php on line 68
+[18-May-2017 12:19:14 Europe/Berlin] PHP Warning: rand() expects exactly 2 parameters, 1 given in /var/www/vps/data-store/docs/api/init.php on line 68
+[18-May-2017 12:19:15 Europe/Berlin] PHP Warning: rand() expects exactly 2 parameters, 1 given in /var/www/vps/data-store/docs/api/init.php on line 68
+[18-May-2017 12:19:23 Europe/Berlin] PHP Warning: rand() expects exactly 2 parameters, 1 given in /var/www/vps/data-store/docs/api/init.php on line 68
+[18-May-2017 12:19:24 Europe/Berlin] PHP Warning: rand() expects exactly 2 parameters, 1 given in /var/www/vps/data-store/docs/api/init.php on line 68
diff --git a/src/autoload.php b/src/autoload.php
@@ -0,0 +1,14 @@
+<?php
+
+if (!defined('APPROOT')) define('APPROOT', dirname(__DIR__));
+if (!defined('DS')) define('DS' , DIRECTORY_SEPARATOR);
+
+// Simple PSR-0 autoloader
+spl_autoload_register(function( $className ) {
+ $path = __DIR__ . DIRECTORY_SEPARATOR;
+ $path .= str_replace("\\", DIRECTORY_SEPARATOR, $className);
+ $path .= '.php';
+ if ( file_exists($path) && is_readable($path) ) {
+ include_once $path;
+ }
+});