diff --git a/.gitignore b/.gitignore index b0baee5..95859c1 100644 --- a/.gitignore +++ b/.gitignore @@ -466,3 +466,8 @@ conf/modules.inc.php conf/routers.inc.php conf/lang.inc.php logs/Exception.log + + +z_dome/ +composer.lock +adminer.php diff --git a/base/config.base.php b/base/config.base.php index feec79c..601dc23 100755 --- a/base/config.base.php +++ b/base/config.base.php @@ -1,18 +1,18 @@ setVersion(1.0); $config->setDatabase([ - DatabaseHandler::DB_TYPE => 'mysql', //please change only if you know what you're doing! this can break a lot. - DatabaseHandler::DB_HOST => '127.0.0.1', - DatabaseHandler::DB_PORT => '3306', //default port is 3306 - DatabaseHandler::DB_USER => 'venom', - DatabaseHandler::DB_PASSWORD => 'venomPassword', - DatabaseHandler::DB_DB => 'venomCMS', - DatabaseHandler::DB_EXTRA => '' // need to start with ';' + Database::DB_TYPE => 'mysql', //please change only if you know what you're doing! this can break a lot. + Database::DB_HOST => '127.0.0.1', + Database::DB_PORT => '3306', //default port is 3306 + Database::DB_USER => 'venom', + Database::DB_PASSWORD => 'venomPassword', + Database::DB_DB => 'venomCMS', + Database::DB_EXTRA => '' // need to start with ';' ]); /** diff --git a/base/lang.base.php b/base/lang.base.php old mode 100644 new mode 100755 diff --git a/base/module.base.php b/base/module.base.php index e51a2b3..815fd85 100755 --- a/base/module.base.php +++ b/base/module.base.php @@ -1,9 +1,4 @@ need to have the Module Class at parent with the init function ;) -$modules = []; - -// register controllers that can handle templates ;) need to have a render function for this -$controllers = [ - 'test' => \Controllers\TestController::class, -]; \ No newline at end of file +//register modules -> only apply Module Path! like Meta now the ModuleLoader search for /modules/Meta/module.php! +$modules = []; \ No newline at end of file diff --git a/base/router.base.php b/base/router.base.php old mode 100644 new mode 100755 index 008287d..10e33d3 --- a/base/router.base.php +++ b/base/router.base.php @@ -11,19 +11,5 @@ if (!isset($venom)) { exit(1); } -$router = new Router('defaultRouter', 1.0, 'api/'); -$router->addRoutes([ - '/test' => [ - 'cl' => Route::class, - 'roles' => ['ROLE_GUEST'], - 'routes' => [ - '*' => [ - "GET" => 'getAll' - ], - '1' => [ - "GET" => 'getAll' - ] - ] - ], -]); -$venom->addRouter('defaultRouter', $router); \ No newline at end of file +$router = new Router(Router::DEFAULT_ROUTER, 1.0, 'api/'); +$venom->addRouter($router); \ No newline at end of file diff --git a/composer.json b/composer.json index 8e4f0cb..0791e73 100755 --- a/composer.json +++ b/composer.json @@ -5,11 +5,19 @@ "authors": [ { "name": "Maurice Grönwoldt", - "email": "mauricegroenwoldt@gmail.com" + "email": "mauricegroenwoldt@gmail.com", + "role": "founder" + }, + { + "name": "Dominic Seela", + "email": "kontakt@engineertrooper.com", + "role": "friendly developer, supporter" } ], "require": { - "ext-pdo": "*" + "ext-pdo": "*", + "ext-http": "*", + "ext-json": "*" }, "autoload": { "psr-4": { diff --git a/conf/.gitkeep b/conf/.gitkeep old mode 100644 new mode 100755 diff --git a/install/db.sql b/install/db.sql index 3e89174..8617f21 100755 --- a/install/db.sql +++ b/install/db.sql @@ -7,6 +7,14 @@ create table if not exists seoData ) comment 'seo url mapping'; +create table if not exists metaTagData +( + id int(255) auto_increment not null unique primary key, + content JSON not null, + isActive tinyint(1) default 1 null +) + comment 'Meta Tag File'; + create table if not exists language ( id int(255) auto_increment not null unique primary key, @@ -27,3 +35,26 @@ create table if not exists data datatype enum ('content', 'form') ) comment 'DataLoader File'; + +create table if not exists users +( + id int(255) auto_increment not null unique primary key, + username varchar(255) not null unique, + firstname varchar(255) not null, + lastname varchar(255) not null, + email varchar(255) not null, + password varchar(255) not null, + token varchar(255) not null, + salt varchar(255) not null, + roleId text default '0' not null, + isActive tinyint(1) default 1 null +) + comment 'User File'; + +create table if not exists roles +( + id int(255) auto_increment not null unique primary key, + name varchar(255) not null unique, + content JSON not null, + isActive tinyint(1) default 1 null +) \ No newline at end of file diff --git a/lang/example.php b/lang/example.php old mode 100644 new mode 100755 diff --git a/logs/.gitkeep b/logs/.gitkeep old mode 100644 new mode 100755 diff --git a/public/.htaccess b/public/.htaccess old mode 100644 new mode 100755 diff --git a/public/admin/.htaccess b/public/admin/.htaccess deleted file mode 100644 index 4d339de..0000000 --- a/public/admin/.htaccess +++ /dev/null @@ -1,7 +0,0 @@ -RewriteEngine On - -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d - -RewriteCond %{REQUEST_URI} (/[^.]*|\.)$ [NC] -RewriteRule .* index.php [L] \ No newline at end of file diff --git a/public/admin/index.php b/public/admin/index.php deleted file mode 100644 index 7ece959..0000000 --- a/public/admin/index.php +++ /dev/null @@ -1,24 +0,0 @@ -isMaintenance()) { - echo 'Currently not available'; - exit; -} -//if devMode is on show all errors! -if ($config->isDevMode()) { - error_reporting(E_ALL); - ini_set('error_reporting', E_ALL); -} -$venom = new Venom(); -Setup::loadRouters($venom); -Setup::loadModules($venom); -$venom->run(); \ No newline at end of file diff --git a/public/content/.gitkeep b/public/content/.gitkeep old mode 100644 new mode 100755 diff --git a/public/index.php b/public/index.php index e1d99e3..046cb5a 100755 --- a/public/index.php +++ b/public/index.php @@ -2,10 +2,12 @@ use Venom\Core\Config; use Venom\Core\Setup; +use Venom\Helper\URLHelper; use Venom\Venom; require_once '../vendor/autoload.php'; -Setup::loadConfig(false); +session_start(); +Setup::loadConfig(URLHelper::getInstance()->isAdminUrl()); Setup::loadLanguage(); $config = Config::getInstance(); @@ -21,4 +23,4 @@ if ($config->isDevMode()) { $venom = new Venom(); Setup::loadRouters($venom); Setup::loadModules($venom); -$venom->run(); \ No newline at end of file +$venom->inject(); \ No newline at end of file diff --git a/public/theme/admin/css/admin-panel.css b/public/theme/admin/css/admin-panel.css new file mode 100755 index 0000000..041b30e --- /dev/null +++ b/public/theme/admin/css/admin-panel.css @@ -0,0 +1 @@ +main{display:flex;height:100vh;overflow:hidden}main h1{margin-top:30px;margin-bottom:20px}main h2{margin-top:35px;margin-bottom:25px}main h3{margin-top:20px;margin-bottom:15px}main h4{margin-top:15px;margin-bottom:10px}main.nav-open .menu{transform:translateX(0)}main.nav-open .app{transform:translateX(220px)}main.nav-open .app .nav-toggle span{transition:width .3s;width:0}main.nav-open .app .nav-toggle span:before{transform:translateY(8px) rotate(-135deg)}main.nav-open .app .nav-toggle span:after{transform:translateY(-8px) rotate(135deg)}.app{transform:translateX(0);transition:transform .4s;flex-grow:1;overflow-y:auto;margin:.6rem .8rem;width:100%;max-height:100%;background:rgba(27,27,27,.5);position:relative}.app .nav-toggle{position:absolute;cursor:pointer;left:1rem;height:25px;width:25px}.app .nav-toggle span{transform:translateY(21px)}.app .nav-toggle span,.app .nav-toggle span:after,.app .nav-toggle span:before{border-radius:1px;height:2px;width:25px;background:#fff;position:absolute;content:'';transition:transform .5s,width .4s ease-in}.app .nav-toggle span:before{top:-8px}.app .nav-toggle span:after{bottom:-8px}.menu{width:220px;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);height:100%;position:fixed;z-index:1;top:0;left:0;overflow-x:hidden;transition:.4s;transform:translateX(-220px);display:flex;flex-direction:column}.menu .logo{text-align:center;font-family:monospace}.menu div[data-link]{padding:.75rem .5rem;position:relative}.menu div[data-link]:after{background-color:#3949ab;content:"";position:absolute;left:0;bottom:0;height:3px;width:100%;transform:scaleX(0);transition:transform .4s;transform-origin:left}.menu div[data-link]:hover:after{transform:scaleX(1)}.menu div[data-link]:last-child{margin-top:auto}.menu div[data-link].active{font-weight:700}.content-area{width:calc(100% - 20px);padding-top:30px;margin:0 auto;display:block}.content-area header{display:block;text-align:center}.content-area header h2{margin:25px 0}.content-area .back-arrow{width:36px;height:36px}.content-area textarea{background:rgba(27,27,27,.5);color:#fff;margin:15px 0 0 0;font-family:sans-serif;font-size:1.1rem;min-width:100%}.content-area .modules div{padding:6px 20px 6px 0}.content-area .add-new,.content-area .overview{width:100%}.content-area .overview div[data-link]{margin-right:10px;padding:10px;background-color:rgba(0,0,0,.3)}.content-area .overview div[data-link]:hover{background-color:rgba(0,0,0,.5)}.content-area .overview .icon{display:inline-block}.content-area .add-new{padding-top:25px}@media only screen and (min-width:768px){.content-area{width:calc(100% - 40px)}.content-area .flexbox{display:flex}.content-area .overview{flex-grow:1;width:60%}.content-area .add-new{padding-top:0;flex-grow:1;width:40%}}@media only screen and (min-width:1024px){.content-area{max-width:860px;padding-top:0;margin:0 0 0 20px}}@media only screen and (min-width:1024px){main,main.nav-open{display:flex}main .app,main.nav-open .app{transform:translateX(0)}main .menu,main.nav-open .menu{position:relative;transform:translateX(0)}main .nav-toggle,main.nav-open .nav-toggle{display:none}}.role-edit .privileges .name{font-size:1.15rem} \ No newline at end of file diff --git a/public/theme/admin/css/login.css b/public/theme/admin/css/login.css new file mode 100755 index 0000000..92a1cd4 --- /dev/null +++ b/public/theme/admin/css/login.css @@ -0,0 +1 @@ +body,html{display:flex;justify-content:center;align-items:center}.hide{display:none!important}#login-background{background-color:rgba(31,31,31,.25);-webkit-filter:blur(10px);filter:blur(10px);position:absolute;top:0;left:0;width:100%;height:100%}login{display:flex;position:relative;max-width:860px;width:90%;height:400px;padding:1rem;border-radius:10px 40px;overflow:hidden;box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23)}#login{z-index:1;display:flex;flex-direction:column;align-items:center;width:100%}#login .logo{width:50%;margin-bottom:20px}#login .error-message{background-color:#c51162;padding:1rem;font-size:.7rem;display:block;width:50%}#login .input-group{width:50%}#login a{text-decoration:none;color:#fff}#login a:hover{color:#3949ab} \ No newline at end of file diff --git a/public/theme/admin/css/style.css b/public/theme/admin/css/style.css new file mode 100755 index 0000000..913f3cd --- /dev/null +++ b/public/theme/admin/css/style.css @@ -0,0 +1 @@ +:focus{outline:0}*{box-sizing:border-box}body,html{width:100vw;height:100vh;overflow:hidden;padding:0;margin:0;background:#1f2857;background:linear-gradient(144deg,#1f2954 25%,#000 50%,#5e002c 80%);background-attachment:fixed;color:#fff;font-family:sans-serif;font-size:16px}.hide{display:none!important}.icon{width:1em;height:1em;vertical-align:middle;font-size:1em;shape-rendering:geometricPrecision;transition:transform .5s cubic-bezier(.22,.61,.36,1);stroke-width:5px;text-align:center;margin-right:.5rem}.icon.block{display:block}.spacer{margin-top:20px}[data-link]{cursor:pointer}[data-link] *{pointer-events:none}.btn{border:none;background:#3949ab radial-gradient(circle at 0 0,#3949ab 0,rgba(0,0,0,.2) 100%) no-repeat;color:#fff;padding:7px 1.1rem;margin:10px 0;cursor:pointer;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:4px;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);overflow:hidden;display:flex;justify-content:center;align-items:center;transition:.5s}.btn--outline{background:0 0;border:1px solid #3949ab}.btn:focus{box-shadow:0 3px 8px 1px rgba(0,0,0,.4)}.btn--valid{background:#39ab48 radial-gradient(circle at 0 0,#39ab48 0,rgba(0,0,0,.2) 100%) no-repeat}.btn--accent{background:#ff0089 radial-gradient(circle at 0 0,#ff0089 0,rgba(0,0,0,.2) 100%) no-repeat}.btn--warn{background:#f87229 radial-gradient(circle at 0 0,#f87229 0,rgba(0,0,0,.2) 100%) no-repeat}.btn-fab{border-radius:2rem;width:2em;height:2em;padding:5px}.btn-ripple{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;background:0 0}.btn-ripple__effect{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);opacity:1;width:200%;height:0;padding-bottom:200%;border-radius:50%;background:rgba(190,190,190,.3);-webkit-animation:a-ripple .4s ease-in;animation:a-ripple .4s ease-in}.btn-ripple__effect.to-remove{-webkit-animation:remove-ripple .2s linear;animation:remove-ripple .2s linear}.btn--outline .btn-ripple__effect{background:rgba(57,73,171,.5);z-index:-1}.btn__content{z-index:1;font-weight:400;pointer-events:none}@-webkit-keyframes remove-ripple{from{opacity:1}to{opacity:0}}@keyframes remove-ripple{from{opacity:1}to{opacity:0}}@-webkit-keyframes a-ripple{0%{opacity:0;padding-bottom:0;width:0}25%{opacity:1}100%{width:200%;padding-bottom:200%}}@keyframes a-ripple{0%{opacity:0;padding-bottom:0;width:0}25%{opacity:1}100%{width:200%;padding-bottom:200%}}.btn-line{text-align:right}.btn-line button{margin-right:5px;margin-left:5px;display:inline-block}.btn-line button:last-child{margin-right:0}v-input input{background-color:rgba(0,0,0,.4);border:none;border-bottom:2px solid #3949ab;color:#fff;padding:calc(.75rem - 1px)}v-input input:focus,v-input.focus input{border-color:#ff0089}v-input.valid input{border-color:#39ab48}v-input.invalid input{border-color:#cf1b1b}v-input{display:flex;flex-direction:column;margin-bottom:5px;position:relative;padding-top:.7rem}v-input.no-space{padding-top:.1rem}v-input label{font-size:.7rem;position:absolute;top:.7rem;left:5px;height:1.5rem;pointer-events:none;vertical-align:middle;transform:translateY(50%);color:#dcdcdc;transition:all .2s ease-out}v-input input:focus~label,v-input.focus label{top:0;left:0;transform:translateY(0);font-size:.6rem}v-input .error{display:none}v-input.invalid .error{margin-top:2px;display:block;font-size:.7rem;color:#f32f32}v-switch{display:flex;flex-direction:row;align-items:center}v-switch input{position:absolute;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}v-switch label{display:block;border-radius:10px;width:40px;height:20px;background-color:#e9e9e9;position:relative;cursor:pointer;margin-right:.5rem}v-switch label:after{content:'';background-color:#c51162;position:absolute;top:2px;left:2px;height:16px;width:16px;border-radius:10px;transition:.5s}v-switch input:checked+label:after{transform:translateX(20px);background-color:#007769}.loader{display:flex;justify-content:center;align-items:center;width:100vw;height:100vh;position:absolute;top:0;z-index:10000;background-color:rgba(0,0,0,.4);opacity:1;transition:opacity .3s}.loader.hide{display:block;opacity:0;pointer-events:none}.loader .spinner{-webkit-animation:rotator 1.4s linear infinite;animation:rotator 1.4s linear infinite}.loader .path{stroke-dasharray:187;stroke-dashoffset:0;transform-origin:center;-webkit-animation:dash 1.4s ease-in-out infinite,colors 5.6s ease-in-out infinite;animation:dash 1.4s ease-in-out infinite,colors 5.6s ease-in-out infinite}@-webkit-keyframes rotator{from{transform:rotate(0)}to{transform:rotate(270deg)}}@keyframes rotator{from{transform:rotate(0)}to{transform:rotate(270deg)}}@-webkit-keyframes colors{0%{stroke:#4285f4}25%{stroke:#de3e35}50%{stroke:#f7c223}75%{stroke:#1b9a59}100%{stroke:#4285f4}}@keyframes colors{0%{stroke:#4285f4}25%{stroke:#de3e35}50%{stroke:#f7c223}75%{stroke:#1b9a59}100%{stroke:#4285f4}}@-webkit-keyframes dash{0%{stroke-dashoffset:187}50%{stroke-dashoffset:46.75;transform:rotate(135deg)}100%{stroke-dashoffset:187;transform:rotate(450deg)}}@keyframes dash{0%{stroke-dashoffset:187}50%{stroke-dashoffset:46.75;transform:rotate(135deg)}100%{stroke-dashoffset:187;transform:rotate(450deg)}}v-table{display:block}v-table v-table-body,v-table v-table-footer,v-table v-table-header,v-table v-table-row{display:grid;grid-auto-columns:1fr;grid-auto-flow:row;-moz-column-gap:20px;column-gap:20px;overflow:hidden}v-table v-table-header{background-color:#3949ab}v-table v-table-header,v-table v-table-row{grid-auto-flow:column;padding:10px;align-items:center}v-table v-table-body{background:rgba(255,255,255,.1);box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);border-radius:5px}v-table v-table-body v-table-row:nth-child(odd){background-color:rgba(0,0,0,.3)}v-table v-table-body v-table-row:hover{background-color:rgba(0,0,0,.5)}v-label,v-option,v-options,v-select{display:inline-block;box-sizing:border-box}v-select{display:block;position:relative;margin-bottom:.5rem}v-label{padding:.5rem .5rem;background-color:rgba(0,0,0,.5);color:#fff;cursor:pointer;display:block;border-bottom:solid 3px #3949ab;margin-top:.5rem;position:relative}v-label:after{content:'';border:solid #fff;border-width:0 2px 2px 0;width:.5rem;height:.5rem;position:absolute;right:1em;margin-top:.1rem;transform:rotate(45deg);transition:.3s}v-label.open:after{transform:rotate(-135deg);margin-top:.4rem}v-options{position:absolute;display:flex;flex-direction:column;overflow:hidden;max-height:0;top:.5rem;left:.5rem;background-color:#1b1b1b;z-index:1000;cursor:pointer;color:#fff;border-radius:5px;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}v-option{padding:.5rem 1rem .5rem 5px;min-width:240px}v-option:not(:last-child){border-bottom:solid 1px #333}v-option:not([disabled]):hover{background-color:rgba(0,0,0,.3)}v-option[selected]{background-color:#3949ab;color:#fff}v-option[disabled]{color:#aaa} \ No newline at end of file diff --git a/public/theme/admin/icon-sprite.svg b/public/theme/admin/icon-sprite.svg new file mode 100755 index 0000000..c013f46 --- /dev/null +++ b/public/theme/admin/icon-sprite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/theme/admin/images/logo.svg b/public/theme/admin/images/logo.svg new file mode 100755 index 0000000..dfd6f7c --- /dev/null +++ b/public/theme/admin/images/logo.svg @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/public/theme/admin/js/components.js b/public/theme/admin/js/components.js new file mode 100755 index 0000000..f333f23 --- /dev/null +++ b/public/theme/admin/js/components.js @@ -0,0 +1,186 @@ +class Component { + constructor(name) { + this.name = name; + this.start(); + } + + handle(data, ds) { + } + + init() { + } + + getUrl(ds) { + return ''; + } + + start() { + if (window.routerIsReady) { + router.addComponent(this.name || VUtils.tempId(), this); + this.init(); + } else { + window.addEventListener('routerReady', this.start.bind(this)); + } + } +} +class MetaDataComponent extends Component { + constructor() { + super("/metaData"); + this.tpl = "metaDataList"; + this.tpl2 = "metaDataEdit"; + this._url = "/admin/api/metaData"; + } + + async handle(data, ds) { + let meTpl = ds.id ? this.tpl2 : this.tpl; + await tpl.loadTemplate(meTpl); + return await tpl.renderOn(meTpl, data.content); + } + + getUrl(ds) { + let url = this._url; + if (ds.id) { + url += '/' + ds.id; + } + return url; + } +} +class OverviewComponent extends Component { + constructor() { + super("/overview"); + this.tpl = "overview"; + this._url = "/admin/api/overview"; + } + + async handle(data, ds) { + await tpl.loadTemplate(this.tpl); + return await tpl.renderOn(this.tpl, data.content); + } + + getUrl(ds) { + return this._url; + } +} +class PagesComponent extends Component { + constructor() { + super("/pages"); + this.tpl = "pagesList"; + this.tpl2 = "pageEdit"; + this._url = "/admin/api/pages"; + } + + async handle(data, ds) { + let meTpl = ds.id ? this.tpl2 : this.tpl; + await tpl.loadTemplate(meTpl); + return await tpl.renderOn(meTpl, data.content); + } + + getUrl(ds) { + let url = this._url; + if (ds.id) { + url += '/' + ds.id; + } + return url; + } +} +class RolesComponent extends Component { + constructor() { + super("/roles"); + this.tpl = "rolesList"; + this.tpl2 = "roleEdit"; + this._url = "/admin/api/roles"; + } + + async handle(data, ds) { + let meTpl = ds.id ? this.tpl2 : this.tpl; + await tpl.loadTemplate(meTpl); + return await tpl.renderOn(meTpl, data.content); + } + + getUrl(ds) { + let url = this._url; + if (ds.id) { + url += '/' + ds.id; + } + return url; + } +} +class SeoUrlComponent extends Component { + constructor() { + super("/seoUrl"); + this.tpl = "seoUrlList"; + this.tpl2 = "seoUrlEdit"; + this._url = "/admin/api/seoUrl"; + } + + async handle(data, ds) { + let meTpl = ds.id ? this.tpl2 : this.tpl; + await tpl.loadTemplate(meTpl); + return await tpl.renderOn(meTpl, data.content); + } + + getUrl(ds) { + let url = this._url; + if (ds.id) { + url += '/' + ds.id; + } + return url; + } +} +class UsersComponent extends Component { + constructor() { + super("/users"); + this.tpl = "usersList"; + this.tpl2 = "userEdit"; + this._url = "/admin/api/users"; + } + + async handle(data, ds) { + let meTpl = ds.id ? this.tpl2 : this.tpl; + await tpl.loadTemplate(meTpl); + return await tpl.renderOn(meTpl, data.content); + } + + getUrl(ds) { + let url = this._url; + if (ds.id) { + url += '/' + ds.id; + } + return url; + } +} +class VenomStatusComponent extends Component { + constructor() { + super("/venomStatus"); + this.tpl = "venomStatus"; + this._url = "/admin/api/venomStatus"; + } + + async handle(data, ds) { + await tpl.loadTemplate(this.tpl); + return await tpl.renderOn(this.tpl, data.content); + } + + getUrl(ds) { + return this._url; + } +} +(() => { + // init all Components ;) + new MetaDataComponent(); + new OverviewComponent(); + new PagesComponent(); + new RolesComponent(); + new SeoUrlComponent(); + new UsersComponent(); + new VenomStatusComponent(); + + + if (routerIsReady) { + document.body.dispatchEvent(new CustomEvent('triggerRouter')); + } else { + document.addEventListener('routerReady', e => { + document.body.dispatchEvent(new CustomEvent('triggerRouter')); + }) + } +})(); \ No newline at end of file diff --git a/public/theme/admin/js/components.min.js b/public/theme/admin/js/components.min.js new file mode 100755 index 0000000..f9ea5ed --- /dev/null +++ b/public/theme/admin/js/components.min.js @@ -0,0 +1 @@ +class Component{constructor(t){this.name=t,this.start()}handle(t,e){}init(){}getUrl(t){return""}start(){window.routerIsReady?(router.addComponent(this.name||VUtils.tempId(),this),this.init()):window.addEventListener("routerReady",this.start.bind(this))}}class MetaDataComponent extends Component{constructor(){super("/metaData"),this.tpl="metaDataList",this.tpl2="metaDataEdit",this._url="/admin/api/metaData"}async handle(t,e){let n=e.id?this.tpl2:this.tpl;return await tpl.loadTemplate(n),await tpl.renderOn(n,t.content)}getUrl(t){let e=this._url;return t.id&&(e+="/"+t.id),e}}class OverviewComponent extends Component{constructor(){super("/overview"),this.tpl="overview",this._url="/admin/api/overview"}async handle(t,e){return await tpl.loadTemplate(this.tpl),await tpl.renderOn(this.tpl,t.content)}getUrl(t){return this._url}}class PagesComponent extends Component{constructor(){super("/pages"),this.tpl="pagesList",this.tpl2="pageEdit",this._url="/admin/api/pages"}async handle(t,e){let n=e.id?this.tpl2:this.tpl;return await tpl.loadTemplate(n),await tpl.renderOn(n,t.content)}getUrl(t){let e=this._url;return t.id&&(e+="/"+t.id),e}}class RolesComponent extends Component{constructor(){super("/roles"),this.tpl="rolesList",this.tpl2="roleEdit",this._url="/admin/api/roles"}async handle(t,e){let n=e.id?this.tpl2:this.tpl;return await tpl.loadTemplate(n),await tpl.renderOn(n,t.content)}getUrl(t){let e=this._url;return t.id&&(e+="/"+t.id),e}}class SeoUrlComponent extends Component{constructor(){super("/seoUrl"),this.tpl="seoUrlList",this.tpl2="seoUrlEdit",this._url="/admin/api/seoUrl"}async handle(t,e){let n=e.id?this.tpl2:this.tpl;return await tpl.loadTemplate(n),await tpl.renderOn(n,t.content)}getUrl(t){let e=this._url;return t.id&&(e+="/"+t.id),e}}class UsersComponent extends Component{constructor(){super("/users"),this.tpl="usersList",this.tpl2="userEdit",this._url="/admin/api/users"}async handle(t,e){let n=e.id?this.tpl2:this.tpl;return await tpl.loadTemplate(n),await tpl.renderOn(n,t.content)}getUrl(t){let e=this._url;return t.id&&(e+="/"+t.id),e}}class VenomStatusComponent extends Component{constructor(){super("/venomStatus"),this.tpl="venomStatus",this._url="/admin/api/venomStatus"}async handle(t,e){return await tpl.loadTemplate(this.tpl),await tpl.renderOn(this.tpl,t.content)}getUrl(t){return this._url}}new MetaDataComponent,new OverviewComponent,new PagesComponent,new RolesComponent,new SeoUrlComponent,new UsersComponent,new VenomStatusComponent,routerIsReady?document.body.dispatchEvent(new CustomEvent("triggerRouter")):document.addEventListener("routerReady",t=>{document.body.dispatchEvent(new CustomEvent("triggerRouter"))}); \ No newline at end of file diff --git a/public/theme/admin/js/scripts.js b/public/theme/admin/js/scripts.js new file mode 100755 index 0000000..af262ac --- /dev/null +++ b/public/theme/admin/js/scripts.js @@ -0,0 +1,1218 @@ +class VUtils { + static makePublic() { + if (VUtils.isUsed) { + return; + } + this.initHandlers(); + VUtils.isUsed = true; + console.log("[VUtils] is now available in the Global Space! no VUtils. anymore needed"); + } + + static initHandlers() { + window.$ = this.$; + window.$$ = this.$$; + window.tryCatch = this.tryCatch; + VUtils.nodePrototypes(); + } + + static $(selector, from) { + from = from || document; + return from.querySelector(selector); + } + + static $$(selector, from) { + from = from || document; + return from.querySelectorAll(selector); + } + + static tryCatch(data, callback, error) { + data = VUtils.wrap(data, []); + error = error || console.error; + callback = callback || console.log; + try { + callback(...data); + } catch (e) { + error(e); + } + } + + static forEach(items, cb, error) { + for (let i = 0; i < items.length; i++) { + VUtils.tryCatch([items[i], i], cb, error); + } + } + + static get(valueOne, value) { + return this.wrap(valueOne, value); + } + + static mergeKeys(root, target) { + root = root || {}; + let keys = Object.keys(root); + for (let key of keys) { + target[key] = root[key]; + } + return target; + } + + static mergeOptions(target, root) { + root = root || {}; + let keys = Object.keys(root); + for (let key of keys) { + target[key] = VUtils.get(root[key], target[key]); + } + return target; + } + + static wrap(valueOne, valueTwo) { + let x = typeof valueTwo; + if (!(valueOne instanceof Array) && valueTwo instanceof Array) { + return [valueOne]; + } + if (x === 'string' && valueOne instanceof Array) { + return valueOne.join("."); + } + return valueOne === undefined ? valueTwo : valueOne; + } + + static tempId() { + return 'temp_' + Math.random().toString(36).substr(2, 16); + } + + static nodePrototypes() { + Node.prototype.find = function (selector) { + return this.closest(selector); + }; + Node.prototype.createNew = function (tag, options) { + let el = document.createElement(tag); + if (options.classes) { + el.classList.add(...VUtils.get(options.classes, [])); + } + el.id = VUtils.get(options.id, ''); + el.innerHTML = VUtils.get(options.content, ""); + VUtils.mergeKeys(options.dataset, el.dataset); + if (VUtils.get(options.append, true) === true) { + this.appendChild(el); + } + + return el; + }; + Node.prototype.addDelegatedEventListener = function (type, aim, callback, err) { + if (!callback || !type || !aim) + return; + this.addMultiListener(type, (event) => { + let target = event.target; + if (event.detail instanceof HTMLElement) { + target = event.detail; + } + if (target instanceof HTMLElement) { + if (target.matches(aim)) { + VUtils.tryCatch([event, target], callback, err); + } else { + const parent = target.find(aim); + if (parent) { + VUtils.tryCatch([event, parent], callback, err); + } + } + } + }); + }; + Node.prototype.addMultiListener = function (listener, cb, options = {}) { + let splits = listener.split(" "); + for (let split of splits) { + this.addEventListener(split, cb, options); + } + }; + } +} + +VUtils.makePublic(); + + +class VRipple { + constructor(options = {}) { + if (!VUtils.isUsed) { + throw "VRipply is only with Public VUtils usable!" + } + let self = this; + self.options = JSON.parse('{"classes":["btn-ripple__effect"],"target":"body","selector":".btn-ripple"}'); + VUtils.mergeOptions(self.options, options); + if (self.options.selector.indexOf("#") > -1) { + throw "ID's are not allowed as selector!"; + } + this.instanceCheck(); + this.ripples = []; + requestAnimationFrame(this.initHandler.bind(this)); + } + + instanceCheck() { + let opts = this.options; + const rawKey = [opts.target, opts.selector, opts.classes.join(".")].join(" "); + VRipple.instances = VRipple.instances || {}; + VRipple.instances[rawKey] = this; + } + + initHandler() { + let self = this; + let selector = self.options.selector; + let target = $(self.options.target); + target.addDelegatedEventListener('mousedown touchstart', selector, (e, el) => { + let pos = e.touches ? e.touches[0] : e; + let parent = el.parentNode; + let circle = el.createNew('span', self.options); + let bounds = parent.getBoundingClientRect(); + let x = pos.clientX - bounds.left; + let y = pos.clientY - bounds.top; + circle.style.top = y + 'px'; + circle.style.left = x + 'px'; + circle._mouseDown = true; + circle._animationEnded = false; + self.ripples.push(circle); + }); + document.body.addDelegatedEventListener('animationend', '.' + VUtils.get(self.options.classes, ''), self.rippleEnd.bind(self)) + if (!document.body._vRippleInit) { + document.body.addMultiListener('mouseup touchend mouseleave rippleClose', e => { + let keys = Object.keys(VRipple.instances); + for (let key of keys) { + for (let ripple of VRipple.instances[key].ripples) { + self.rippleEnd.bind(VRipple.instances[key])(e, ripple); + } + } + }) + document.body._vRippleInit = true; + } + } + + rippleEnd(ev, el) { + const parent = el.parentNode; + if (parent) { + if (ev.type === 'animationend') { + el._animationEnded = true; + } else { + el._mouseDown = false; + } + if (!el._mouseDown && el._animationEnded) { + if (el.classList.contains('to-remove')) { + el.parentNode.removeChild(el); + this.ripples.splice(this.ripples.indexOf(el), 1) + } else { + el.classList.add('to-remove'); + } + } + } + } +} + +const rippler = new VRipple(); + + + +(function () { + window._openVSelect = null; + + requestAnimationFrame(e => { + document.body.addEventListener('click', ev => { + if (window._openVSelect && ev.target.closest('v-select') !== window._openVSelect) { + window._openVSelect.toggle(false); + } + }) + }) + + class VSelectElement extends HTMLElement { + constructor() { + super(); + let self = this; + self._in = this.attachInternals(); + self._in.role = 'select'; + self.setAttribute('tabindex', 0); + self.update(); + } + + static get formAssociated() { + return true; + } + + static get observedAttributes() { + return ['required', 'validity']; + } + + get required() { + return this.hasAttribute('required'); + } + + set required(flag) { + this.toggleAttribute('required', Boolean(flag)); + } + + get name() { + return this.getAttribute('name'); + } + + set name(val) { + this.toggleAttribute('name', val); + } + + get form() { + return this._in.form; + } + + get options() { + return $$('v-options v-option', this); + } + + get selected() { + return $$('v-options v-option[selected]', this); + } + + update() { + let selected = [], + lbl = $('v-label', this), + fd = new FormData(); + this.selected.forEach(e => { + selected.push(e.innerText); + fd.append(this.name, e.value); + }) + lbl.attributeChangedCallback('value', '', selected.join(", ")); + if (this.required && selected.length === 0) { + this._in.setValidity({customError: true}, "Option is needed"); + } else { + this._in.setValidity({}); + } + this._in.setFormValue(fd); + } + + checkValidity() { + return this._in.checkValidity(); + } + + reportValidity() { + return this._in.reportValidity(); + } + + toggle(open) { + if (window._openVSelect && open) { + window._openVSelect.toggleSelect(false); + } + const options = $('v-options', this); + if (!open || this.isOpen) { + options.style.maxHeight = '0'; + window._openVSelect = false; + this.isOpen = false; + this.update(); + } else { + options.focus(); + let height = 0, + children = options.children; + for (let i = 0; i < children.length; i++) { + height += children[i].offsetHeight; + } + options.style.maxHeight = height + 'px'; + window._openVSelect = this; + this.isOpen = true; + } + let l = $('v-label', this).classList; + if (this.isOpen) { + l.add('open'); + } else { + l.remove('open'); + } + } + } + + class VSelectOptionElement extends HTMLElement { + constructor() { + super(); + this._in = this.attachInternals(); + this._in.role = 'option'; + this.addEventListener('click', e => { + let parent = this.parentNode.parentNode, + select = !this.selected; + if (!parent.hasAttribute('multiple')) { + parent.toggle(false); + for (let item of parent.selected) { + if (item !== this) { + item.removeAttribute('selected'); + } + } + } + if (!this.disabled) { + this.attributeChangedCallback('selected', false, select, true); + this.parentNode.parentNode.update(); + } + }); + } + + static get observedAttributes() { + return ['selected', 'disabled', 'value']; + } + + attributeChangedCallback(name, oldValue, newValue, force) { + if (name === 'selected' && this.hasAttribute('disabled')) { + this.removeAttribute(name); + return; + } + if (name === 'disabled' && newValue === true && this.hasAttribute('selected')) { + this.attributeChangedCallback('selected', false, false); + } + + if (force) { + if (newValue) { + this.setAttribute(name, newValue); + } else { + this.removeAttribute(name); + } + } + this[name] = newValue; + } + } + + class VLabel extends HTMLElement { + constructor() { + super(); + this.empty = this.getAttribute('empty') || ""; + this.innerHTML = this.getAttribute("value") || this.empty; + this.addEventListener('click', this.openPopUp.bind(this)); + } + + static get observedAttributes() { + return ['empty', 'value']; + } + + openPopUp() { + this.parentNode.toggle(true); + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'value') { + this.innerHTML = newValue || this.empty; + } + this[name] = newValue; + } + } + + customElements.define("v-label", VLabel); + customElements.define("v-option", VSelectOptionElement); + customElements.define("v-select", VSelectElement); +})(); + + + +class FormHandler { + constructor(selector, parent, cb, err) { + this.cb = cb || console.log; + this.err = err || console.err; + $(parent).addDelegatedEventListener('submit', selector, this.handleEvent.bind(this)); + } + + handleEvent(e, el) { + e.preventDefault(); + if (el.checkValidity()) { + const url = el.action ?? ''; + if (url === '') { + console.error("No URL Found on Form", el); + return; + } + fetch(el.action, { + method: el.method.toUpperCase(), + credentials: 'same-origin', + body: new FormData(el), + redirect: 'manual' + }).then(res => { + if (!res.ok) { + throw new Error('Network response errored'); + } + return res.json() + }).then(ev => this.cb(ev, el)).catch(ev => this.err(ev, el)); + } else { + VUtils.forEach($$('input', el), ele => { + if (!ele.checkValidity()) { + let parent = ele.parentNode; + parent.classList.remove('valid'); + parent.classList.add('invalid'); + } + }); + } + } +} + +(function () { + class VInput extends HTMLElement { + constructor() { + super(); + let self = this; + self.id = self.id || VUtils.tempId(); + let val = self.innerHTML; + self.innerHTML = ''; + let input = self.input = self.createNew('input', {id: self.id}) + let label = self.createNew('label', {content: self.dataset.label}); + self.createNew('span', {classes: 'error', content: self.dataset.error}); + label.setAttribute('for', self.id); + input.type = self.getAttribute('type') || 'text'; + input.value = val.trim(); + input.required = self.hasAttribute('required'); + input.name = self.getAttribute('name'); + input.addMultiListener('change input', self.cb.bind(self)); + } + + connectedCallback() { + this.cb({currentTarget: this.input}, true); + } + + cb(e, noInvalid) { + let el = e.currentTarget + let errorMessage = $('.error-message', el.find('form')); + if (errorMessage) { + errorMessage.classList.add('hide') + } + let cl = this.classList; + if (el.value === "") { + cl.remove('focus') + } else { + cl.add('focus') + } + if (el.checkValidity()) { + cl.add('valid'); + cl.remove('invalid'); + } else { + if (!noInvalid) { + cl.remove('valid'); + cl.add('invalid'); + } + } + } + } + + class VSwitch extends HTMLElement { + constructor() { + super(); + const id = this.dataset.id || VUtils.tempId(); + $('input', this).id = id; + $('label', this).setAttribute('for', id); + } + } + + customElements.define("v-input", VInput); + customElements.define("v-switch", VSwitch); + + if ($('#login')) { + new FormHandler('form#login', 'body', () => { + location.reload(); + }, (e, el) => { + $('.error-message', el).classList.remove('hide'); + }) + } +})(); +(() => { + class VEdit { + constructor(selector) { + let self = this; + self.editor = selector instanceof HTMLElement ? selector : $(selector); + self.todoOnKey = {}; + self.keys = []; + self.backup = []; + self.taberr = [">", " ", "\n", "<"]; + self.name = 'veditor-' + self.editor.id; + self.init(); + self.selfClosing = ["img", "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "menuitem", "meta", "param", "source", "track"]; + self.restore(); + } + + init() { + let self = this; + self.editor.addEventListener('keydown', self.handleKey.bind(self)); + self.addKey('Tab', self.pressTab.bind(self)); + self.addKey('<', self.addEmptyTags.bind(self)); + self.addKey('ctrl-z', self.undo.bind(self)); + self.addKey('ctrl-s', self.store.bind(self)); + self.addKey('ctrl-shift-S', self.delete.bind(self)); + self.editor.classList.add(self.name, 'veditor') + } + + registerSelfClosing(name) { + this.selfClosing.push(name); + } + + restore() { + let item = localStorage.getItem(this.name); + if (item) { + this.editor.value = item; + } + } + + delete() { + localStorage.removeItem(this.name); + console.log(`[VEdit] Editor: ${this.name} removed`); + } + + store() { + localStorage.setItem(this.name, this.editor.value); + console.log(`[VEdit] Editor: ${this.name} saved`); + } + + handleKey(e) { + let self = this; + if ((e.ctrlKey && e.key === 'Control') + || (e.shiftKey && e.key === 'Shift')) { + return; + } + let key; + if (e.ctrlKey && e.shiftKey) { + key = 'ctrl-shift-' + e.key; + } else if (e.ctrlKey) { + key = 'ctrl-' + e.key; + } + if (key) { + if (this.keys.indexOf(key) !== -1) { + e.preventDefault(); + this.todoOnKey[key](); + return; + } + } + let cont = self.editor.value; + const pos = self.editor.selectionStart + if (self.backup.length > 50) { + self.backup.shift(); + } + self.backup.push([cont, pos]); + if (self.keys.indexOf(e.key) > -1) { + e.preventDefault(); + let w = self.todoOnKey[e.key](pos, cont, this.editor); + w[0].push(cont.substr(pos)) + self.afterWork(w); + } + } + + undo() { + let back = this.backup.pop() || [this.editor.value, this.editor.selectionStart]; + this.editor.value = back[0]; + this.editor.setSelectionRange(back[1], back[1]); + } + + afterWork(data) { + this.setText(data[0].join("")); + this.editor.setSelectionRange(data[1], data[1]); + } + + setText(text) { + this.editor.value = text; + } + + addKey(name, func) { + this.todoOnKey[name] = func; + this.keys.push(name); + } + + addEmptyTags(pos, cont, e) { + return [[cont.substr(0, pos), '<>'], pos + 1]; + } + + pressTab(pos, cont, e) { + let self = this; + let sub, prevContent, moveTo = pos; + if (pos === 0 || self.taberr.indexOf(cont[pos - 1]) !== -1) { + sub = ` `; + moveTo += 4; + prevContent = cont.substr(0, pos); + } else if (self.taberr.indexOf(cont[pos - 1]) === -1) { + let i = 2; + while (self.taberr.indexOf(cont[pos - i]) === -1 && pos - i > 0) { + i++; + } + if (pos - i > 0) { + i -= 1; + } + let gen = self.generateTag(cont.substr(pos - i, i).trim()); + sub = gen[0]; + moveTo = pos - i + gen[1]; + prevContent = cont.substr(0, pos - i); + } + return [[prevContent, sub], moveTo] + } + + generateTag(sub) { + let raw, + groups = {'.': [], '#': []}, + keys = Object.keys(groups), + cGroup = 'cl', + split = sub.split(/([#.])/g); + raw = split.shift(); + for (let item of split) { + if (keys.indexOf(item) > -1) { + cGroup = item; + continue; + } + groups[cGroup].push(item); + } + let second = ''; + if (groups["."].length > 0) { + second += ` class="${groups["."].join(" ")}"`; + } + if (groups['#'].length > 0) { + second += ` id="${groups['#'].join("-")}"`; + } + const c = this.selfClosing; + let close = ''; + if (c.indexOf(raw.trim()) === -1) { + close = ``; + } + let pre = `<${raw}${second}>`; + return [`${pre}${close}`, pre.length]; + } + } + + class VEditor extends HTMLElement { + constructor() { + super(); + this.editor = document.createElement('textarea'); + this.editor.innerHTML = this.innerHTML; + this.editor.id = this.getAttribute('name'); + for (let attribute of this.attributes) { + this.editor.setAttribute(attribute.name, attribute.value); + } + this.innerHTML = ''; + this.appendChild(this.editor); + this.edit = new VEdit(this.editor); + } + + connectedCallback() { + this.edit.restore(); + } + + disconnectedCallback() { + this.edit.save(); + } + } + + customElements.define("v-editor", VEditor); +})(); +(() => { + const mobileBreakpoint = 1023; + const main = $('main'); + + function isMobileDevice() { + return window.matchMedia("(max-width: " + mobileBreakpoint + "px)").matches; + } + window.isMobileDevice = isMobileDevice; + $('body').addDelegatedEventListener('click', '.nav-toggle', (e, el) => { + if (isMobileDevice()) { + main.classList.toggle('nav-open'); + } + }); + +})(); +(() => { + class Router { + constructor(options) { + let self = this; + self.options = options; + document.body.addDelegatedEventListener('click', '[data-link]', (e, el) => { + e.preventDefault(); + $$('[data-link].active').forEach(ex => (ex.classList.remove('active'))); + let loader = $('.loader').classList; + loader.remove('hide'); + this.handleRouting(el.dataset).then(e => { + loader.add('hide'); + el.classList.add('active'); + }); + }) + document.body.addEventListener('triggerRouter', e => { + let storage = sessionStorage.getItem('url') || JSON.stringify({data: {link: $('[data-link].active').dataset.link}}); + this.handle(storage); + }) + window.addEventListener('popstate', e => { + this.handle(e.state); + }) + self.components = {}; + window.dispatchEvent(new CustomEvent('routerReady')); + window.routerIsReady = true; + } + + handle(item) { + if (!item) { + return; + } + item = JSON.parse(item); + this.handleRouting(item.data).then(r => { + let it = $('[data-link="' + item.data.link + '"]'); + if (it) { + it.classList.add('active'); + } + }); + } + + async handleRouting(dataset) { + try { + let url = dataset.link, + comp = this.components[url]; + if (url === "") return null; + if (comp) url = comp.getUrl(dataset); + let data = await this.handleRequest(url, true); // we know the admin backend only returns json so we cheat a bit :) + if (data.reload) { + return location.reload(); + } + comp = comp || this.components[data.component] || null; + if (comp) { + sessionStorage.setItem('url', JSON.stringify({data: dataset})); + comp.handle(data, dataset).then(r => { + $(this.options.toReplace).innerHTML = r; + history.pushState(JSON.stringify({data: dataset}), document.title); + }); + } else { + await alert("Error"); + } + return null; + } catch (e) { + return e; + } + } + + async handleRequest(url, forceJSON) { + url = url.trim(); + if (url === '') return; + // await ;) + return await fetch(url, { + credentials: "same-origin" + }).then(res => { + if (!res.ok) { + throw `URL is Status: ${res.status}`; + } + let c = res.headers.get("Content-Type"); + if (c.indexOf('json') !== -1 || forceJSON) return res.json(); + if (c.indexOf('text') !== -1) return res.text(); + return res.blob(); + }).catch(err => { + console.error(err) + return null; + }); + } + + addComponent(name, component) { + this.components[name] = component; + } + } + + window.router = new Router({ + toReplace: '.content-area' + }) +})(); +'use strict'; + +class VTpeLCore { + constructor(options = {}) { + this.templates = {}; + this.dir = options.path || '/tpl/'; + this.suffix = options.suffix || 'tpl'; + this.path = options.template || `${this.dir}%s.${this.suffix}`; + this.cache = options.cache === undefined ? true : options.cache; + } + + async loadTemplate(name) { + if (this.templates[name]) { + return null; + } + let path = this.path.replace('%s', name); + let rawData = await fetch(path, {cache: "force-cache"}); + if (rawData.ok) { + let data = await rawData.text(); + this.addTpl(name, data); + } + return null; + } + + async loadArray(names) { + for (let name of names) { + await this.loadTemplate(name); + } + } + + addTpl(name, content) { + let temp = this.templates[name] = new VTpeLTemplate(name, content, this); + temp.parseContent(this.cache); + } + + async renderOn(name, data) { + if (this.templates[name]) { + return await this.templates[name].render(data); + } + return ''; + } +} + +'use strict'; + +const VParserTypes = { + content: 0, + variable: 1, + for: 2, + forEach: 3, + forContent: 4, + forEnd: 5, + if: 6, + ifContent: 7, + ifEnd: 8, + assign: 9, + include: 10, + none: -1, +}; + + +class VTpeLParser { + constructor(name, content) { + let self = this; + self.name = name; + self.legex = content.trim(); + self.index = 0; + self.content = ''; + self.parsed = []; + self.contexts = [0]; + } + + tokenize() { + let self = this; + for (self.index = 0; self.index < self.legex.length; self.index++) { + let i = self.index, + char = self.legex.charAt(i); + if (self.nextContains('/*', i, true)) { + self.extract('*/', VParserTypes.none) + } else if (self.nextContains('// ', i, true)) { + self.extract("\n", VParserTypes.none); + } else if (self.nextContains('', VParserTypes.none); + } else if (self.nextContains('{for(', i, true)) { + self.extract(')}', VParserTypes.for); + self.contexts.push(VParserTypes.for); + } else if (self.nextContains('{include(', i, true)) { + self.extract(')}', VParserTypes.include); + self.contexts.push(VParserTypes.include); + } else if (self.nextContains('{foreach(', i, true)) { + self.extract(')}', VParserTypes.forEach); + self.contexts.push(VParserTypes.forEach); + } else if (self.nextContains('{/for}', i, true)) { + self.addType(VParserTypes.forEnd); + self.contexts.pop(); + } else if (self.nextContains('{if(', i, true)) { + self.extract(')}', VParserTypes.if); + self.contexts.push(VParserTypes.if); + } else if (self.nextContains('{/if}', i, true)) { + self.addType(VParserTypes.ifEnd); + self.contexts.pop(); + } else if (self.nextContains('$${', i, true)) { + self.extract('}', VParserTypes.assign); + } else if (self.nextContains('${', i, true)) { + self.extract('}', VParserTypes.variable); + } else { + self.content += char; + } + } + self.addType(VParserTypes.content); + return self.parsed; + } + + addType(type) { + let self = this; + let content = self.content.replace(/^\n+|\n+$/g, ''), + instructions = self.findInstructions(type); + self.content = ''; + if (type !== VParserTypes.none) { + if (type === VParserTypes.content && content === '') { + return null; + } + return self.parsed.push({ + content: content, + type: type, + context: self.contexts[self.contexts.length - 1], + instructions: instructions + }); + } + return null; + } + + nextContains(find, index, add = false) { + let count = this.nextContainsRaw(this.legex, find, index); + if (add && count > 0) { + this.index += count; + } + return count > 0 || count === -1; + } + + nextContainsRaw(raw, find, index) { + if (typeof find === "string") { + find = find.split(""); + } + let count = find.length; + if (count < 1) { + return -1; + } + for (let i = 0; i < count; i++) { + let nc = raw.charAt(index + i); + if ((find[i] === '\n' && nc === undefined)) { + return count; + } + if (find[i] !== nc) { + return 0; + } + } + return count; + } + + extract(findUntil = '}', type = 1) { + let self = this; + self.addType(0); + findUntil = findUntil.split("") + let content = '', + index = self.index, + legex = self.legex, + firstFind = findUntil.shift(); + for (let i = self.index; i < legex.length; i++) { + let char = legex.charAt(i); + if (char === firstFind && self.nextContains(findUntil, i + 1)) { + console.debug(`[Parser][${index} > ${i}] >> ${content}`); + self.index = i + findUntil.length; + self.content = content.trim(); + self.addType(type); + return; + } + content += char; + } + if (firstFind === "\n") { + self.index = legex.length; + self.content = content.trim(); + self.addType(type); + return + } + throw 'Template variable at Position: ' + index + ' not closed!'; + } + + // @todo implement split... is needed for if statements and math stuff or get it stupid! + getOperator(string) { + let operators = []; + for (let i = 0; i < string.length; i++) { + if (this.nextContainsRaw(string, "(", i)) { + let innerCon = ''; + for (let x = 0; x < string.length; x++) { + let char = string.charAt(i + x); + if (char === ')') { + break; + } + innerCon += char; + } + operators = [...operators, this.getOperator(innerCon)]; + } else { + } + } + return operators; + } + + findInstructions(type) { + if (type === VParserTypes.if) { + return this.getOperator(this.content); + } + return []; + } +} + +'use strict'; + +class VTepLInterpreter { + constructor(parser, core) { + this.parser = parser; + this.data = []; + this.content = ''; + this.core = core; + } + + async render(data) { + let self = this; + self.data = data; + let newData = await self.interpreter(self.parser.parsed); + self.data = []; + return newData[0]; + } + + async interpreter(parsed, index = 0) { + let self = this; + let types = VParserTypes; + let tplCont = ''; + for (let i = index; i < parsed.length; i++) { + let item = parsed[i], + content = item.content; + switch (item.type) { + case types.content: + tplCont += content; + break; + case types.variable: + tplCont += self.getVariable(content) + break; + case types.assign: + let data = content.split("="), + key = data.shift(); + self.setVariable(data.join("=").trim(), key.trim()); + break; + case types.forEach: + let d = await this.handleForEach(item, parsed, i); + i = d[0]; + tplCont += d[1]; + break; + case types.for: + let fd = await this.handleFor(item, parsed, i); + i = fd[0]; + tplCont += fd[1]; + break; + case types.if: + let id = await this.handleIf(item, parsed, i); + i = id[0]; + tplCont += id[1]; + break; + case types.include: + tplCont += await this.handleInclude(item); + break; + case types.ifEnd: + tplCont += content; + return [tplCont, i]; + case types.forEnd: + tplCont += content; + return [tplCont, i]; + default: + console.warn("Invalid Type found"); + break; + } + } + return [tplCont, parsed.length]; + } + + getVariable(variable) { + variable = variable.toString(); + if (this.data[variable]) { + return this.data[variable]; + } + let split = variable.split("."), + prevVar = this.data; + for (let i = 0; i < split.length; i++) { + prevVar = prevVar[split[i]] || prevVar; + } + if (typeof prevVar === 'string') { + return prevVar; + } + if (typeof prevVar === 'number') { + return prevVar.toString(); + } + return ''; + } + + setVariable(value, variable) { + let c = this.getVariable(value); + if (c !== '') { + value = c; + } + this.data[variable] = value; + } + + async handleForEach(item, parsed, i) { + let content = item.content.split(" as "); + let root = this.getVariable(content[0].trim()); + let addTo = 0, + isInvalid = false; + if (root === '') { + isInvalid = true; + root = {invalid: "true"}; + } + let d = Object.keys(root), + raw = '', + varContent = content[1].trim().split(","); + for (let x of d) { + if (varContent.length === 2) { + this.setVariable(x, varContent[1]); + } + this.setVariable(root[x], varContent[0]); + let data = await this.interpreter(parsed, i + 1); + addTo = data[1]; + raw += data[0]; + } + if (isInvalid) { + raw = ''; + } + return [addTo, raw]; + } + + async handleInclude(item) { + let split = item.content.split(";"), + name = split.shift(), + data = {}; + await this.core.loadTemplate(name); + for (let item of split) { + let d = item.split("="), + index = d.shift(), + dat = d.join("="); + if (dat.startsWith("$")) { + dat = this.getVariable(dat.substr(1, dat.length)); + } + data[index] = dat; + } + return await this.core.renderOn(name, data); + } + + async handleFor(item, parsed, ind) { + let content = item.content.split(" as "), + addTo = 0, + count = content[0].trim().split(".."), + max = parseInt(count[1]), + min = parseInt(count[0]), + newContent = ''; + for (let i = min; i < max; i++) { + this.setVariable(i.toString(), content[1]); + let data = await this.interpreter(parsed, ind + 1); + addTo = data[1]; + newContent += data[0]; + } + return [addTo, newContent]; + } + + async handleIf(item, parsed, i) { + let data = await this.interpreter(parsed, i + 1); + return [data[1], data[0]]; + } +} + +'use strict'; + +class VTpeLTemplate { + constructor(name, content, core) { + this.name = name; + this.tpl = content; + this.parser = new VTpeLParser(name, content); + this.core = core; + } + + async render(data = {}) { + return await new VTepLInterpreter(this.parser, this.core).render(data); + } + + parseContent(cache) { + if (cache) { + let storage = localStorage.getItem("vtepl-"+this.name); + if (storage) { + this.parser.parsed = JSON.parse(storage); + return; + } + } + this.parser.tokenize(); + if (cache) { + localStorage.setItem("vtepl-"+this.name, JSON.stringify(this.parser.parsed)); + } + } +} + +(() => { + window.tpl = new VTpeLCore({ + template: '/admin/api/templateLoader?tpl=%s', + cache: !document.body.hasAttribute('debug') + }); + //preload includes to make sure they are loaded always :) + window.tpl.loadArray([ + 'includes/btn', + 'includes/input', + 'includes/select', + 'includes/svg', + 'includes/switch' + ]) +})(); \ No newline at end of file diff --git a/public/theme/admin/js/scripts.min.js b/public/theme/admin/js/scripts.min.js new file mode 100755 index 0000000..9a43a4a --- /dev/null +++ b/public/theme/admin/js/scripts.min.js @@ -0,0 +1 @@ +class VUtils{static makePublic(){VUtils.isUsed||(this.initHandlers(),VUtils.isUsed=!0,console.log("[VUtils] is now available in the Global Space! no VUtils. anymore needed"))}static initHandlers(){window.$=this.$,window.$$=this.$$,window.tryCatch=this.tryCatch,VUtils.nodePrototypes()}static $(e,t){return(t=t||document).querySelector(e)}static $$(e,t){return(t=t||document).querySelectorAll(e)}static tryCatch(e,t,i){e=VUtils.wrap(e,[]),i=i||console.error,t=t||console.log;try{t(...e)}catch(e){i(e)}}static forEach(e,t,i){for(let s=0;s{let n=e.target;if(e.detail instanceof HTMLElement&&(n=e.detail),n instanceof HTMLElement)if(n.matches(t))VUtils.tryCatch([e,n],i,s);else{const r=n.find(t);r&&VUtils.tryCatch([e,r],i,s)}})},Node.prototype.addMultiListener=function(e,t,i={}){let s=e.split(" ");for(let e of s)this.addEventListener(e,t,i)}}}VUtils.makePublic();class VRipple{constructor(e={}){if(!VUtils.isUsed)throw"VRipply is only with Public VUtils usable!";let t=this;if(t.options=JSON.parse('{"classes":["btn-ripple__effect"],"target":"body","selector":".btn-ripple"}'),VUtils.mergeOptions(t.options,e),t.options.selector.indexOf("#")>-1)throw"ID's are not allowed as selector!";this.instanceCheck(),this.ripples=[],requestAnimationFrame(this.initHandler.bind(this))}instanceCheck(){let e=this.options;const t=[e.target,e.selector,e.classes.join(".")].join(" ");VRipple.instances=VRipple.instances||{},VRipple.instances[t]=this}initHandler(){let e=this,t=e.options.selector;$(e.options.target).addDelegatedEventListener("mousedown touchstart",t,(t,i)=>{let s=t.touches?t.touches[0]:t,n=i.parentNode,r=i.createNew("span",e.options),a=n.getBoundingClientRect(),o=s.clientX-a.left,l=s.clientY-a.top;r.style.top=l+"px",r.style.left=o+"px",r._mouseDown=!0,r._animationEnded=!1,e.ripples.push(r)}),document.body.addDelegatedEventListener("animationend","."+VUtils.get(e.options.classes,""),e.rippleEnd.bind(e)),document.body._vRippleInit||(document.body.addMultiListener("mouseup touchend mouseleave rippleClose",t=>{let i=Object.keys(VRipple.instances);for(let s of i)for(let i of VRipple.instances[s].ripples)e.rippleEnd.bind(VRipple.instances[s])(t,i)}),document.body._vRippleInit=!0)}rippleEnd(e,t){t.parentNode&&("animationend"===e.type?t._animationEnded=!0:t._mouseDown=!1,!t._mouseDown&&t._animationEnded&&(t.classList.contains("to-remove")?(t.parentNode.removeChild(t),this.ripples.splice(this.ripples.indexOf(t),1)):t.classList.add("to-remove")))}}const rippler=new VRipple;!function(){window._openVSelect=null,requestAnimationFrame(e=>{document.body.addEventListener("click",e=>{window._openVSelect&&e.target.closest("v-select")!==window._openVSelect&&window._openVSelect.toggle(!1)})});class e extends HTMLElement{constructor(){super();let e=this;e._in=this.attachInternals(),e._in.role="select",e.setAttribute("tabindex",0),e.update()}static get formAssociated(){return!0}static get observedAttributes(){return["required","validity"]}get required(){return this.hasAttribute("required")}set required(e){this.toggleAttribute("required",Boolean(e))}get name(){return this.getAttribute("name")}set name(e){this.toggleAttribute("name",e)}get form(){return this._in.form}get options(){return $$("v-options v-option",this)}get selected(){return $$("v-options v-option[selected]",this)}update(){let e=[],t=$("v-label",this),i=new FormData;this.selected.forEach(t=>{e.push(t.innerText),i.append(this.name,t.value)}),t.attributeChangedCallback("value","",e.join(", ")),this.required&&0===e.length?this._in.setValidity({customError:!0},"Option is needed"):this._in.setValidity({}),this._in.setFormValue(i)}checkValidity(){return this._in.checkValidity()}reportValidity(){return this._in.reportValidity()}toggle(e){window._openVSelect&&e&&window._openVSelect.toggleSelect(!1);const t=$("v-options",this);if(!e||this.isOpen)t.style.maxHeight="0",window._openVSelect=!1,this.isOpen=!1,this.update();else{t.focus();let e=0,i=t.children;for(let t=0;t{let t=this.parentNode.parentNode,i=!this.selected;if(!t.hasAttribute("multiple")){t.toggle(!1);for(let e of t.selected)e!==this&&e.removeAttribute("selected")}this.disabled||(this.attributeChangedCallback("selected",!1,i,!0),this.parentNode.parentNode.update())})}static get observedAttributes(){return["selected","disabled","value"]}attributeChangedCallback(e,t,i,s){"selected"===e&&this.hasAttribute("disabled")?this.removeAttribute(e):("disabled"===e&&!0===i&&this.hasAttribute("selected")&&this.attributeChangedCallback("selected",!1,!1),s&&(i?this.setAttribute(e,i):this.removeAttribute(e)),this[e]=i)}}class i extends HTMLElement{constructor(){super(),this.empty=this.getAttribute("empty")||"",this.innerHTML=this.getAttribute("value")||this.empty,this.addEventListener("click",this.openPopUp.bind(this))}static get observedAttributes(){return["empty","value"]}openPopUp(){this.parentNode.toggle(!0)}attributeChangedCallback(e,t,i){"value"===e&&(this.innerHTML=i||this.empty),this[e]=i}}customElements.define("v-label",i),customElements.define("v-option",t),customElements.define("v-select",e)}();class FormHandler{constructor(e,t,i,s){this.cb=i||console.log,this.err=s||console.err,$(t).addDelegatedEventListener("submit",e,this.handleEvent.bind(this))}handleEvent(e,t){if(e.preventDefault(),t.checkValidity()){if(""===(t.action??""))return void console.error("No URL Found on Form",t);fetch(t.action,{method:t.method.toUpperCase(),credentials:"same-origin",body:new FormData(t),redirect:"manual"}).then(e=>{if(!e.ok)throw new Error("Network response errored");return e.json()}).then(e=>this.cb(e,t)).catch(e=>this.err(e,t))}else VUtils.forEach($$("input",t),e=>{if(!e.checkValidity()){let t=e.parentNode;t.classList.remove("valid"),t.classList.add("invalid")}})}}!function(){class e extends HTMLElement{constructor(){super();let e=this;e.id=e.id||VUtils.tempId();let t=e.innerHTML;e.innerHTML="";let i=e.input=e.createNew("input",{id:e.id}),s=e.createNew("label",{content:e.dataset.label});e.createNew("span",{classes:"error",content:e.dataset.error}),s.setAttribute("for",e.id),i.type=e.getAttribute("type")||"text",i.value=t.trim(),i.required=e.hasAttribute("required"),i.name=e.getAttribute("name"),i.addMultiListener("change input",e.cb.bind(e))}connectedCallback(){this.cb({currentTarget:this.input},!0)}cb(e,t){let i=e.currentTarget,s=$(".error-message",i.find("form"));s&&s.classList.add("hide");let n=this.classList;""===i.value?n.remove("focus"):n.add("focus"),i.checkValidity()?(n.add("valid"),n.remove("invalid")):t||(n.remove("valid"),n.add("invalid"))}}class t extends HTMLElement{constructor(){super();const e=this.dataset.id||VUtils.tempId();$("input",this).id=e,$("label",this).setAttribute("for",e)}}customElements.define("v-input",e),customElements.define("v-switch",t),$("#login")&&new FormHandler("form#login","body",()=>{location.reload()},(e,t)=>{$(".error-message",t).classList.remove("hide")})}(),(()=>{class e{constructor(e){let t=this;t.editor=e instanceof HTMLElement?e:$(e),t.todoOnKey={},t.keys=[],t.backup=[],t.taberr=[">"," ","\n","<"],t.name="veditor-"+t.editor.id,t.init(),t.selfClosing=["img","area","base","br","col","embed","hr","img","input","link","menuitem","meta","param","source","track"],t.restore()}init(){let e=this;e.editor.addEventListener("keydown",e.handleKey.bind(e)),e.addKey("Tab",e.pressTab.bind(e)),e.addKey("<",e.addEmptyTags.bind(e)),e.addKey("ctrl-z",e.undo.bind(e)),e.addKey("ctrl-s",e.store.bind(e)),e.addKey("ctrl-shift-S",e.delete.bind(e)),e.editor.classList.add(e.name,"veditor")}registerSelfClosing(e){this.selfClosing.push(e)}restore(){let e=localStorage.getItem(this.name);e&&(this.editor.value=e)}delete(){localStorage.removeItem(this.name),console.log(`[VEdit] Editor: ${this.name} removed`)}store(){localStorage.setItem(this.name,this.editor.value),console.log(`[VEdit] Editor: ${this.name} saved`)}handleKey(e){let t,i=this;if(e.ctrlKey&&"Control"===e.key||e.shiftKey&&"Shift"===e.key)return;if(e.ctrlKey&&e.shiftKey?t="ctrl-shift-"+e.key:e.ctrlKey&&(t="ctrl-"+e.key),t&&-1!==this.keys.indexOf(t))return e.preventDefault(),void this.todoOnKey[t]();let s=i.editor.value;const n=i.editor.selectionStart;if(i.backup.length>50&&i.backup.shift(),i.backup.push([s,n]),i.keys.indexOf(e.key)>-1){e.preventDefault();let t=i.todoOnKey[e.key](n,s,this.editor);t[0].push(s.substr(n)),i.afterWork(t)}}undo(){let e=this.backup.pop()||[this.editor.value,this.editor.selectionStart];this.editor.value=e[0],this.editor.setSelectionRange(e[1],e[1])}afterWork(e){this.setText(e[0].join("")),this.editor.setSelectionRange(e[1],e[1])}setText(e){this.editor.value=e}addKey(e,t){this.todoOnKey[e]=t,this.keys.push(e)}addEmptyTags(e,t,i){return[[t.substr(0,e),"<>"],e+1]}pressTab(e,t,i){let s,n,r=this,a=e;if(0===e||-1!==r.taberr.indexOf(t[e-1]))s=" ",a+=4,n=t.substr(0,e);else if(-1===r.taberr.indexOf(t[e-1])){let i=2;for(;-1===r.taberr.indexOf(t[e-i])&&e-i>0;)i++;e-i>0&&(i-=1);let o=r.generateTag(t.substr(e-i,i).trim());s=o[0],a=e-i+o[1],n=t.substr(0,e-i)}return[[n,s],a]}generateTag(e){let t,i={".":[],"#":[]},s=Object.keys(i),n="cl",r=e.split(/([#.])/g);t=r.shift();for(let e of r)s.indexOf(e)>-1?n=e:i[n].push(e);let a="";i["."].length>0&&(a+=` class="${i["."].join(" ")}"`),i["#"].length>0&&(a+=` id="${i["#"].join("-")}"`);let o="";-1===this.selfClosing.indexOf(t.trim())&&(o=``);let l=`<${t}${a}>`;return[`${l}${o}`,l.length]}}class t extends HTMLElement{constructor(){super(),this.editor=document.createElement("textarea"),this.editor.innerHTML=this.innerHTML,this.editor.id=this.getAttribute("name");for(let e of this.attributes)this.editor.setAttribute(e.name,e.value);this.innerHTML="",this.appendChild(this.editor),this.edit=new e(this.editor)}connectedCallback(){this.edit.restore()}disconnectedCallback(){this.edit.save()}}customElements.define("v-editor",t)})(),(()=>{const e=$("main");function t(){return window.matchMedia("(max-width: 1023px)").matches}window.isMobileDevice=t,$("body").addDelegatedEventListener("click",".nav-toggle",(i,s)=>{t()&&e.classList.toggle("nav-open")})})(),window.router=new class{constructor(e){this.options=e,document.body.addDelegatedEventListener("click","[data-link]",(e,t)=>{e.preventDefault(),$$("[data-link].active").forEach(e=>e.classList.remove("active"));let i=$(".loader").classList;i.remove("hide"),this.handleRouting(t.dataset).then(e=>{i.add("hide"),t.classList.add("active")})}),document.body.addEventListener("triggerRouter",e=>{let t=sessionStorage.getItem("url")||JSON.stringify({data:{link:$("[data-link].active").dataset.link}});this.handle(t)}),window.addEventListener("popstate",e=>{this.handle(e.state)}),this.components={},window.dispatchEvent(new CustomEvent("routerReady")),window.routerIsReady=!0}handle(e){e&&(e=JSON.parse(e),this.handleRouting(e.data).then(t=>{let i=$('[data-link="'+e.data.link+'"]');i&&i.classList.add("active")}))}async handleRouting(e){try{let t=e.link,i=this.components[t];if(""===t)return null;i&&(t=i.getUrl(e));let s=await this.handleRequest(t,!0);return s.reload?location.reload():(i=i||this.components[s.component]||null,i?(sessionStorage.setItem("url",JSON.stringify({data:e})),i.handle(s,e).then(t=>{$(this.options.toReplace).innerHTML=t,history.pushState(JSON.stringify({data:e}),document.title)})):await alert("Error"),null)}catch(e){return e}}async handleRequest(e,t){if(""!==(e=e.trim()))return await fetch(e,{credentials:"same-origin"}).then(e=>{if(!e.ok)throw"URL is Status: "+e.status;let i=e.headers.get("Content-Type");return-1!==i.indexOf("json")||t?e.json():-1!==i.indexOf("text")?e.text():e.blob()}).catch(e=>(console.error(e),null))}addComponent(e,t){this.components[e]=t}}({toReplace:".content-area"});class VTpeLCore{constructor(e={}){this.templates={},this.dir=e.path||"/tpl/",this.suffix=e.suffix||"tpl",this.path=e.template||`${this.dir}%s.${this.suffix}`,this.cache=void 0===e.cache||e.cache}async loadTemplate(e){if(this.templates[e])return null;let t=this.path.replace("%s",e),i=await fetch(t,{cache:"force-cache"});if(i.ok){let t=await i.text();this.addTpl(e,t)}return null}async loadArray(e){for(let t of e)await this.loadTemplate(t)}addTpl(e,t){(this.templates[e]=new VTpeLTemplate(e,t,this)).parseContent(this.cache)}async renderOn(e,t){return this.templates[e]?await this.templates[e].render(t):""}}const VParserTypes={content:0,variable:1,for:2,forEach:3,forContent:4,forEnd:5,if:6,ifContent:7,ifEnd:8,assign:9,include:10,none:-1};class VTpeLParser{constructor(e,t){let i=this;i.name=e,i.legex=t.trim(),i.index=0,i.content="",i.parsed=[],i.contexts=[0]}tokenize(){let e=this;for(e.index=0;e.index0&&(this.index+=s),s>0||-1===s}nextContainsRaw(e,t,i){"string"==typeof t&&(t=t.split(""));let s=t.length;if(s<1)return-1;for(let n=0;n ${o}] >> ${s}`),i.index=o+e.length,i.content=s.trim(),void i.addType(t);s+=l}if("\n"===a)return i.index=r.length,i.content=s.trim(),void i.addType(t);throw"Template variable at Position: "+n+" not closed!"}getOperator(e){let t=[];for(let i=0;igetUrl(), ['/admin/', '/admin'])) { + http_response_code(404); + $this->tpl = 'async'; + } + + $isLogin = Security::get()->hasPermission("admin", RoleEntity::TYPE_READ); + $renderer->addVar('isLoggedIn', $isLogin); + if (!$isLogin) { + Asset::get()->addCSS('login', 'login.css'); + } else { + Asset::get()->addCSS('admin', 'admin-panel.css'); + } + Asset::get()->addCSS('styles', 'style.css', 1); + Asset::get()->addJS('scripts', 'scripts.min.js', 1); + // Components are the Rendering-Pipeline to know how each Admin-Component needs to be rendered + Asset::get()->addJS('components', 'components.min.js', 5); + + return true; + } + + public function getTemplate(): string + { + return $this->tpl; + } +} \ No newline at end of file diff --git a/src/Venom/Admin/AdminRouterInit.php b/src/Venom/Admin/AdminRouterInit.php new file mode 100755 index 0000000..e183a9b --- /dev/null +++ b/src/Venom/Admin/AdminRouterInit.php @@ -0,0 +1,38 @@ +getRouter(Router::ADMIN_ROUTER)->addRoutes(self::getRoutes()); + } + + public static function getRoutes(): array + { + return [ + '/login' => new Route(LoginRoute::class, [ + '*' => [ + "POST" => 'login' + ], + '1' => [ + "GET" => 'handle' + ] + ]), + '/templateLoader' => new Route(TemplateLoader::class, [ + '*' => [ + "GET" => 'handle' + ], + ]), + ]; + } +} \ No newline at end of file diff --git a/src/Venom/Admin/Routes/LoginRoute.php b/src/Venom/Admin/Routes/LoginRoute.php new file mode 100755 index 0000000..c3ebc2f --- /dev/null +++ b/src/Venom/Admin/Routes/LoginRoute.php @@ -0,0 +1,27 @@ +login(); + return true; + } + + public function handle($fnc): bool + { + if ($fnc === 'logout') { + Security::get()->logout(); + echo '{"reload": true}'; + die(); + } + return true; + } +} \ No newline at end of file diff --git a/src/Venom/Admin/Routes/TemplateLoader.php b/src/Venom/Admin/Routes/TemplateLoader.php new file mode 100755 index 0000000..187665a --- /dev/null +++ b/src/Venom/Admin/Routes/TemplateLoader.php @@ -0,0 +1,25 @@ +isDevMode()) { + header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + (60 * 60 * 60 * 30))); + header('Cache-Control: public'); + } + $id = ArgumentHandler::get()->getItem('tpl', '..'); + if (strpos($id, '..')) { + return false; + } + echo TemplateUtil::includeTemplate('jsTemplates/' . $id, '.tpl'); + die(); + } +} \ No newline at end of file diff --git a/src/Venom/Routing/SeoController.php b/src/Venom/Controller/SeoController.php old mode 100644 new mode 100755 similarity index 94% rename from src/Venom/Routing/SeoController.php rename to src/Venom/Controller/SeoController.php index 969f94d..c9a8275 --- a/src/Venom/Routing/SeoController.php +++ b/src/Venom/Controller/SeoController.php @@ -1,12 +1,12 @@ $item) { $this->arguments[htmlspecialchars($key)] = htmlspecialchars($item); + $this->post[htmlspecialchars($key)] = htmlspecialchars($item); } } @@ -41,4 +43,14 @@ class ArgumentHandler { return isset($this->arguments[$key]); } + + public function getPostItem(string $key, $default = null) + { + return $this->post[$key] ?? $default; + } + + public function hasPostItem(string $key): bool + { + return isset($this->post[$key]); + } } \ No newline at end of file diff --git a/src/Venom/Core/Config.php b/src/Venom/Core/Config.php old mode 100644 new mode 100755 index fc48444..3468b8e --- a/src/Venom/Core/Config.php +++ b/src/Venom/Core/Config.php @@ -2,7 +2,8 @@ namespace Venom\Core; -use Venom\Models\ConfigObject; +use Venom\Core\Database\DatabaseHandler; +use Venom\Entities\ConfigObject; class Config { diff --git a/src/Venom/Core/Database/Database.php b/src/Venom/Core/Database/Database.php new file mode 100755 index 0000000..05ff4c0 --- /dev/null +++ b/src/Venom/Core/Database/Database.php @@ -0,0 +1,102 @@ +db != null) { + return; + } + $dsn = '%s:host=%s;dbname=%s;port=%s'; + $connectString = sprintf($dsn, $data[self::DB_TYPE], $data[self::DB_HOST], $data[self::DB_DB], $data[self::DB_PORT] . $data[self::DB_EXTRA]); + try { + $this->db = new PDO($connectString, $data[self::DB_USER], $data[self::DB_PASSWORD]); + } catch (PDOException $e) { + trigger_error($e->getMessage()); + die($e->getCode()); + } + } + + public function getOne(string|EasyQuery $query, array $args = []): ?DatabaseObject + { + $sql = $query; + if ($query instanceof EasyQuery) { + $sql = $query->getQuery(); + $args = $query->getArgs(); + } + $data = $this->getAll($sql, $args); + if (count($data) > 0) { + return $data[0]; + } + return null; + } + + public function getAll(string|EasyQuery $query, array $args = []): array + { + $sql = $query; + if ($query instanceof EasyQuery) { + $sql = $query->getQuery(); + $args = $query->getArgs(); + } + $stmt = $this->db->prepare($sql); + $stmt->setFetchMode(PDO::FETCH_CLASS, DatabaseObject::class); + $stmt->execute($args); + return $stmt->fetchAll(); + } + + public function execute(string|EasyQuery $query, array $args = []): bool + { + $sql = $query; + if ($query instanceof EasyQuery) { + $sql = $query->getQuery(); + $args = $query->getArgs(); + } + $stmt = $this->db->prepare($sql); + return $stmt->execute($args); + } + + public function createStatement($query): bool|PDOStatement + { + $stmt = $this->db->prepare($query); + $stmt->setFetchMode(PDO::FETCH_CLASS, DatabaseObject::class); // set to default fetch-mode :D + return $stmt; + } + + public function setClass($stmt, $class) + { + $stmt->setFetchMode(PDO::FETCH_CLASS, $class); + } + + public function start() + { + $this->db->beginTransaction(); + } + + public function commit() + { + $this->db->commit(); + } + + public function rollBack() + { + $this->db->rollBack(); + } +} \ No newline at end of file diff --git a/src/Venom/Core/Database/DatabaseHandler.php b/src/Venom/Core/Database/DatabaseHandler.php new file mode 100755 index 0000000..943f7fe --- /dev/null +++ b/src/Venom/Core/Database/DatabaseHandler.php @@ -0,0 +1,38 @@ +db = new Database(); + } + + public static function get(): Database + { + return self::getInstance()->db; + } + + public static function getInstance(): DatabaseHandler + { + if (self::$instance === null) { + self::$instance = new DatabaseHandler(); + } + return self::$instance; + } + + public static function getEntityManager($entityClass): EntityManager + { + $instance = self::getInstance(); + // i dont make sure this class exist because the user should do this ;) + if (!isset($instance->cache[$entityClass])) { + $instance->cache[$entityClass] = new EntityManager($entityClass, self::get()); + } + return $instance->cache[$entityClass]; + } +} \ No newline at end of file diff --git a/src/Venom/Core/Database/EasyQuery.php b/src/Venom/Core/Database/EasyQuery.php new file mode 100755 index 0000000..2e25efc --- /dev/null +++ b/src/Venom/Core/Database/EasyQuery.php @@ -0,0 +1,220 @@ +whereStmt = $statement; + return $this; + } + + public function setHaving(string $statement): static + { + $this->havingStmt = $statement; + return $this; + } + + public function setLimit(int $limit): static + { + $this->limit = $limit; + return $this; + } + + public function setOffset(int $offset): static + { + $this->offset = $offset; + return $this; + } + + public function addField($field, $as = ""): static + { + if ($as !== "") { + $field .= " AS " . $as; + } + $this->fields[] = $field; + return $this; + } + + public function addFields(array $fields): static + { + foreach ($fields as $field) { + $this->fields[] = $field; + } + return $this; + } + + public function where($key, $value, $type = "AND"): static + { + $this->where[] = [$key, $type]; + $this->args[":" . $key] = $value; + return $this; + } + + public function having($key, $value, $type = "AND"): static + { + $this->having[] = [$key, $type]; + $this->args[":" . $key] = $value; + return $this; + } + + public function orderBy(string $key, int $mode = self::ORDER_ASC): static + { + $this->order[] = $mode === self::ORDER_DESC ? $key . " DESC" : $key; + return $this; + } + + public function groupBy(string $key): static + { + $this->groupBy[] = $key; + return $this; + } + + public function setArg($key, $value): static + { + $this->args[":" . $key] = $value; + return $this; + } + + // returns a Query + + public function addArgAndField($key, $value): static + { + $this->args[":" . $key] = $value; + $this->fields[] = $key; + return $this; + } + + public function buildSelect(): static + { + // we build an easyQuery Builder that can very easy stuff + $query = self::createSelect($this->fields, $this->tableName); + if (count($this->where) > 0) { + $this->whereStmt = $this->parseStmt($this->where, $this->whereStmt); + } + if (count($this->having) > 0) { + $this->havingStmt = $this->parseStmt($this->having, $this->havingStmt); + } + if ($this->whereStmt !== "") { + $query .= " WHERE " . $this->whereStmt; + } + if (count($this->groupBy)) { + $query .= " GROUP BY " . implode(", ", $this->groupBy); + } + if ($this->havingStmt !== "") { + $query .= " HAVING " . $this->havingStmt; + } + if (count($this->order)) { + $query .= " ORDER BY " . implode(", ", $this->order); + } + if ($this->offset > 0) { + $query .= " OFFSET " . $this->offset; + } + if ($this->limit > 0) { + $query .= " LIMIT " . $this->limit; + } + $this->query = $query; + return $this; + } + + public function buildInsertQuery(): static + { + $query = "INSERT INTO " . $this->tableName; + $joinedFields = implode(", ", $this->fields); + $values = implode(", ", array_keys($this->args)); + $query .= "(" . $joinedFields . ") VALUES (" . $values . ")"; + $this->query = $query; + return $this; + } + + public function buildUpdateQuery(): static + { + $query = "UPDATE " . $this->tableName . " SET "; + $setFields = []; + foreach ($this->fields as $field) { + $setFields[] = $field . " = :" . $field; + } + $query .= implode(", ", $setFields); + if (count($this->where) > 0) { + $this->whereStmt = $this->parseStmt($this->where, $this->whereStmt); + } + if ($this->whereStmt !== "") { + $query .= " WHERE " . $this->whereStmt; + } + $this->query = $query; + return $this; + } + + public function buildDeleteQuery(): static + { + $query = "DELETE FROM " . $this->tableName; + if (count($this->where) > 0) { + $this->whereStmt = $this->parseStmt($this->where, $this->whereStmt); + } + if ($this->whereStmt !== "") { + $query .= " WHERE " . $this->whereStmt; + } + $this->query = $query; + return $this; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getArgs(): array + { + return $this->args; + } + + public function getFields(): array + { + return $this->fields; + } + + private function parseStmt($items, $default = ""): string + { + $query = $default; + foreach ($items as $item) { + if ($query !== "") { + $query .= " " . $item[1] . " "; + } + if ($item[1] === self::WHERE_NOT && $query === "") { + $query .= "NOT "; + } + $query .= $item[0] . " = :" . $item[0]; + } + return $query; + } +} \ No newline at end of file diff --git a/src/Venom/Core/Database/Entity.php b/src/Venom/Core/Database/Entity.php new file mode 100755 index 0000000..053f5ca --- /dev/null +++ b/src/Venom/Core/Database/Entity.php @@ -0,0 +1,137 @@ +loadedFields as $var) { + $keyValue[$var] = $this->$var; + } + return $keyValue; + } + + public function getFieldsToWrite(): array + { + if ($this->fields !== null) { + return $this->fields; + } + $localBlacklist = array_merge(["primaryKey", "tableName", "loadedFields", "blackList", "fields"], $this->blackList); + $allLoaded = in_array("*", $this->loadedFields); + $vars = get_object_vars($this); + foreach ($vars as $key => $var) { + if (in_array($key, $localBlacklist)) { + unset($vars[$key]); + } + } + + if (!$allLoaded) { + foreach ($vars as $key => $var) { + if (!in_array($key, $this->loadedFields)) { + unset($vars[$key]); + } + } + } + unset($vars[$this->primaryKey]); + $this->fields = $vars; + return $this->fields; + } + + public function save(): bool + { + $this->preSave(); + $primaryKey = $this->primaryKey; + $fields = $this->removeEmptyFields($this->getFieldsToWrite()); + $query = new EasyQuery(static::$tableName); + foreach ($fields as $key => $field) { + $query->addArgAndField($key, $field); + } + if ($this->$primaryKey === "") { + $query->buildInsertQuery(); + } else { + $query->where($primaryKey, $this->$primaryKey)->buildUpdateQuery(); + } + return DatabaseHandler::get()->execute($query); + } + + public function load($fields = ['*'], ?EasyQuery $query = null): static + { + if ($query === null) { + $primaryKey = $this->primaryKey; + $query = new EasyQuery(static::$tableName, $fields); + $query->where($primaryKey, $this->$primaryKey)->setLimit(1)->buildSelect(); + + } else { + $query->setLimit(1)->buildSelect(); + } + + $item = DatabaseHandler::get()->getOne($query); + if ($item === null) { + return $this; + } + $lazy = $item->getData(); + $this->id = $item->id; + foreach ($lazy as $key => $item) { + $this->$key = $item; + } + $this->fields = null; + $this->loadedFields = array_merge($this->loadedFields, $query->getFields()); + $this->postLoad(); + return $this; + } + + public function __set($name, $value) + { + // Implement your own if you want to override this behaviour! + throw new RuntimeException("Write to Property: $name that is not Available in the Entity!"); + } + + public function delete() + { + $key = $this->primaryKey; + $query = new EasyQuery(self::$tableName); + $query->setArg($this->primaryKey, $this->$key)->buildDeleteQuery(); + DatabaseHandler::get()->execute($query->getQuery(), $query->getArgs()); + } + + public function jsonSerialize(): array + { + return $this->getLoadedFieldValues(); + } + + public function preSave() + { + } + + public function postLoad() + { + } + + private function removeEmptyFields(array $vars): array + { + foreach ($vars as $name => $item) { + if (empty($item) && $name != $this->primaryKey) { + unset($vars[$name]); + } + } + return $vars; + } +} \ No newline at end of file diff --git a/src/Venom/Core/Database/EntityManager.php b/src/Venom/Core/Database/EntityManager.php new file mode 100755 index 0000000..9aef7d5 --- /dev/null +++ b/src/Venom/Core/Database/EntityManager.php @@ -0,0 +1,136 @@ +classType; + $this->entities[] = $ent; + return $ent; + } + + public function addEntity(Entity $entity) + { + $this->entities[] = $entity; + } + + public function removeEntity(Entity $entity) + { + foreach ($this->entities as $key => $item) { + if ($entity === $item) { + unset($this->entities[$key]); + break; + } + } + } + + public function findBy($key, $value): ?Entity + { + foreach ($this->entities as $entity) { + if ($entity->$key === $value) { + return $entity; + } + } + return null; + } + + public function saveAll(): bool + { + if (count($this->entities) === 0) { + return true; + } + try { + $this->db->start(); + foreach ($this->entities as $entity) { + $entity->save(); + } + $this->db->commit(); + } catch (Exception $ex) { + trigger_error($ex->getMessage()); + $this->db->rollBack(); + return false; + } + return true; + } + + public function deleteEntities(): bool + { + try { + $this->db->start(); + foreach ($this->entities as $entity) { + $entity->delete(); + } + $this->db->commit(); + } catch (Exception $ex) { + trigger_error($ex->getMessage()); + $this->db->rollBack(); + return false; + } + return true; + } + + public function clearAll() + { + $this->entities = []; + } + + public function load(string|EasyQuery $query, $args = [], array $fields = ["*"]) + { + $sql = $query; + if ($query instanceof EasyQuery) { + $query->buildSelect(); + $sql = $query->getQuery(); + $args = $query->getArgs(); + $fields = $query->getFields(); + } + $stmt = $this->db->createStatement($sql); + $this->db->setClass($stmt, $this->classType); + if ($stmt->execute($args)) { + /** @var Entity[] $all */ + $all = $stmt->fetchAll(); + foreach ($all as $item) { + $item->loadedFields = $fields; + $item->postLoad(); + $this->addEntity($item); + } + } + } + + public function execute($query): bool + { + return $this->db->execute($query); + } + + public function getAll(): array + { + return $this->entities; + } + + public function getFirst(): ?Entity + { + return $this->entities[0] ?? null; + } + + private function addEntities(array $entities) + { + foreach ($entities as $entity) { + $this->entities[] = $entity; + } + } +} \ No newline at end of file diff --git a/src/Venom/Core/DatabaseHandler.php b/src/Venom/Core/DatabaseHandler.php deleted file mode 100755 index 72e5ffe..0000000 --- a/src/Venom/Core/DatabaseHandler.php +++ /dev/null @@ -1,74 +0,0 @@ -db != null) { - return; - } - $dsn = '%s:host=%s;dbname=%s;port=%s'; - $connectString = sprintf($dsn, $data[self::DB_TYPE], $data[self::DB_HOST], $data[self::DB_DB], $data[self::DB_PORT] . $data[self::DB_EXTRA]); - try { - $this->db = new PDO($connectString, $data[self::DB_USER], $data[self::DB_PASSWORD]); - } catch (PDOException $e) { - trigger_error($e->getMessage()); - die($e->getCode()); - } - } - - public function getOne(string $query, array $args): ?DatabaseObject - { - $data = $this->getAll($query, $args); - if (count($data) > 0) { - return $data[0]; - } - return null; - } - - public function getAll(string $query, array $args): array - { - $stmt = $this->db->prepare($query); - $stmt->setFetchMode(PDO::FETCH_CLASS, DatabaseObject::class); - $stmt->execute($args); - return $stmt->fetchAll(); - } - - public function execute(string $query, array $args): bool - { - $stmt = $this->db->prepare($query); - return $stmt->execute($args); - } -} \ No newline at end of file diff --git a/src/Venom/Core/Language.php b/src/Venom/Core/Language.php old mode 100644 new mode 100755 index edcb0ba..3ab30e2 --- a/src/Venom/Core/Language.php +++ b/src/Venom/Core/Language.php @@ -5,7 +5,8 @@ namespace Venom\Core; use RuntimeException; -use Venom\Models\DatabaseObject; +use Venom\Core\Database\DatabaseHandler; +use Venom\Entities\DatabaseObject; class Language { @@ -20,7 +21,7 @@ class Language public function initLang() { - $lang = ArgumentHandler::get()->getItem("lang", $this->defaultLang->shortTag); + $lang = ArgumentHandler::get()->getItem("lang", $this->defaultLang->shortTag ?? 'de'); //check if language exists $data = DatabaseHandler::get()->getOne("select id from language where shortTag = :shortTag", [ ':shortTag' => $lang diff --git a/src/Venom/Core/Module.php b/src/Venom/Core/Module.php old mode 100644 new mode 100755 index 746d386..f050e89 --- a/src/Venom/Core/Module.php +++ b/src/Venom/Core/Module.php @@ -3,10 +3,17 @@ namespace Venom\Core; - interface Module { - public function register(): bool; - - public function init(): void; + const NAME = "name"; + const AUTHOR = "author"; + const SECURE = "secure"; + const ROUTE = "routes"; + const ADMIN_ROUTE = "adminRoutes"; + const DESC = "description"; + const TEMPLATES = "templates"; + const ADMIN_TEMPLATES = "adminTemplates"; + const CONTROLLER = "controllers"; + const TEMPLATE_PATH = "tplPath"; + const ACTIVE = "isActive"; } \ No newline at end of file diff --git a/src/Venom/Core/ModuleLoader.php b/src/Venom/Core/ModuleLoader.php new file mode 100755 index 0000000..bf2fbc2 --- /dev/null +++ b/src/Venom/Core/ModuleLoader.php @@ -0,0 +1,72 @@ +isAdmin(); + if ($isAdmin) { + self::registerRoutes($module, + $venom, + $module[Module::ADMIN_ROUTE], + $venom->getRouter(Router::ADMIN_ROUTER) + ); + TemplateUtil::getInstance()->addTemplates($module[Module::ADMIN_TEMPLATES], $module[Module::TEMPLATE_PATH]); + } else { + self::registerRoutes($module, + $venom, + $module[Module::ROUTE], + $venom->getRouter(Router::DEFAULT_ROUTER) + ); + TemplateUtil::getInstance()->addTemplates($module[Module::TEMPLATES], $module[Module::TEMPLATE_PATH]); + } + $venom->addControllers($module[Module::CONTROLLER]); + return true; + } + + public static function registerRoutes(array $module, Venom $venom, array $routes, Router $router) + { + foreach ($routes as $key => $route) { + /** @var Route $route */ + $route->module = $module[Module::NAME]; + $route->isSecure = $module[Module::SECURE]; + $route->venom = $venom; + $router->addRoute($key, $route); + } + } +} \ No newline at end of file diff --git a/src/Venom/Core/Registry.php b/src/Venom/Core/Registry.php old mode 100644 new mode 100755 index 486fdd5..c46797e --- a/src/Venom/Core/Registry.php +++ b/src/Venom/Core/Registry.php @@ -3,7 +3,7 @@ namespace Venom\Core; -use Venom\Routing\SeoController; +use Venom\Controller\SeoController; /** * Singleton Class... hold current URL => can diff --git a/src/Venom/Core/Setup.php b/src/Venom/Core/Setup.php old mode 100644 new mode 100755 index c35d1a1..20fde4a --- a/src/Venom/Core/Setup.php +++ b/src/Venom/Core/Setup.php @@ -39,9 +39,6 @@ class Setup if (isset($modules)) { $venom->initModules($modules); } - if (isset($controllers)) { - $venom->initControllers($controllers); - } } public static function loadRouters(Venom $venom): void diff --git a/src/Venom/Models/ConfigObject.php b/src/Venom/Entities/ConfigObject.php old mode 100644 new mode 100755 similarity index 85% rename from src/Venom/Models/ConfigObject.php rename to src/Venom/Entities/ConfigObject.php index 931ce4e..24388c3 --- a/src/Venom/Models/ConfigObject.php +++ b/src/Venom/Entities/ConfigObject.php @@ -1,10 +1,12 @@ id = $id; - $this->type = $type; - $this->raw = $raw; - $this->generated = $generated; } public function getId(): string diff --git a/src/Venom/Models/DatabaseObject.php b/src/Venom/Entities/DatabaseObject.php old mode 100644 new mode 100755 similarity index 69% rename from src/Venom/Models/DatabaseObject.php rename to src/Venom/Entities/DatabaseObject.php index 26a8cd4..2ec6d6a --- a/src/Venom/Models/DatabaseObject.php +++ b/src/Venom/Entities/DatabaseObject.php @@ -1,15 +1,16 @@ id, $obj->value * also the option to print it in csv format ; as delimiter * @package Venom\Database */ -class DatabaseObject +class DatabaseObject extends Entity { private array $data = []; @@ -26,19 +27,24 @@ class DatabaseObject $this->data[$name] = $value; } - public function __isset($name) + public function __isset($name): bool { return isset($this->data[$name]); } - public function toString() + public function toString(): string { return implode(';', $this->data); } - public function getHead() + public function getHead(): string { $keys = array_keys($this->data); return implode(';', $keys); } + + public function getData(): array + { + return $this->data; + } } \ No newline at end of file diff --git a/src/Venom/Entities/RoleEntity.php b/src/Venom/Entities/RoleEntity.php new file mode 100755 index 0000000..670e8a3 --- /dev/null +++ b/src/Venom/Entities/RoleEntity.php @@ -0,0 +1,59 @@ +id === -1) { + return true; + } + if ($type === self::TYPE_NO) { + return true; + } + if (!isset($this->roles[$module]) && $type) { + return false; + } + $mod = $this->roles[$module]; + return $mod["type"] === $type; + } + + + public function postLoad() + { + if (!empty($this->content)) { + $this->roles = json_decode($this->content); + } + } + + public function preSave() + { + $this->content = json_encode($this->roles); + } + + public function load($fields = ['*'], ?EasyQuery $query = null): static + { + if ($this->id === -1 || $this->id === 0) { + return $this; + } + return parent::load($fields, $query); + } + + +} \ No newline at end of file diff --git a/src/Venom/Entities/User.php b/src/Venom/Entities/User.php new file mode 100755 index 0000000..283c376 --- /dev/null +++ b/src/Venom/Entities/User.php @@ -0,0 +1,57 @@ +roleEntity === null) { + $this->roleEntity = new RoleEntity(); + $this->roleEntity->id = $this->roleId; + $this->roleEntity->load(); + } + return $this->roleEntity->hasPermission($module, $type); + } + + public function postLoad() + { + $this->loaded = true; + } + + + public function isLoaded(): bool + { + return $this->loaded; + } + + public function loadUser() + { + $eq = new EasyQuery(User::$tableName, ["*"]); + $eq->where("username", $this->username) + ->where("id", $this->id, EasyQuery::WHERE_OR); + $this->load([], $eq); + return true; + } +} \ No newline at end of file diff --git a/src/Venom/Exceptions/ExceptionHandler.php b/src/Venom/Exceptions/ExceptionHandler.php old mode 100644 new mode 100755 diff --git a/src/Venom/Helper/AdminHelper.php b/src/Venom/Helper/AdminHelper.php new file mode 100755 index 0000000..eb42ceb --- /dev/null +++ b/src/Venom/Helper/AdminHelper.php @@ -0,0 +1,37 @@ + $content, + 'component' => $component + ]; + + if ($shouldReload) { + $response['reload'] = true; + } + if ($extra) { + $response['extra'] = $extra; + } + echo json_encode($response); + die(); + } + + public static function sendStatus(bool $isSuccess, string $message = "") + { + if ($message == "") { + $message = $isSuccess ? "Operation Success" : "Operation failed"; + } + echo json_encode([ + "status" => $isSuccess ? 'success' : 'failed', + "message" => $message + ]); + die(); + } +} \ No newline at end of file diff --git a/src/Venom/Helper/MetaGenerator.php b/src/Venom/Helper/MetaGenerator.php old mode 100644 new mode 100755 index e6dd9dd..55a4a49 --- a/src/Venom/Helper/MetaGenerator.php +++ b/src/Venom/Helper/MetaGenerator.php @@ -4,11 +4,40 @@ namespace Venom\Helper; +use Venom\Core\ArgumentHandler; +use Venom\Core\DatabaseHandler; + /** * Class MetaGenerator * @package Venom\Helper */ class MetaGenerator { + private array $container = []; + private string $id; + public function __construct() + { + $this->id = (string)ArgumentHandler::get()->getItem('metaId', '-1'); + } + + public function loadById(): void + { + if ($this->id === '-1') { + return; + } + $db = DatabaseHandler::get(); + $data = $db->getOne('select content from metaTagData where id = :id', [':id' => $this->id]); + if ($data !== null) { + $this->container = json_decode($data->content ?? '', true); + $this->container = array_merge([], $this->container); + } + } + + public function render(): void + { + foreach ($this->container as $key => $value) { + echo ''; + } + } } \ No newline at end of file diff --git a/src/Venom/Helper/TemplateUtil.php b/src/Venom/Helper/TemplateUtil.php new file mode 100755 index 0000000..283e9a5 --- /dev/null +++ b/src/Venom/Helper/TemplateUtil.php @@ -0,0 +1,75 @@ +isAdmin()) { + $base = 'base'; + $theme = 'admin'; + } else { + $data = Config::getInstance()->getRenderer(); + $theme = $data->theme; + $base = $data->baseFile ?? 'base'; + } + $this->baseTemplate = $base . '.php'; + $this->templateDir = __DIR__ . '/../../../tpl/' . $theme . '/'; + } + + public static function getInstance(): TemplateUtil + { + if (self::$instance === null) { + self::$instance = new TemplateUtil(); + } + return self::$instance; + } + + public function getDir(): string + { + return $this->templateDir; + } + + public function getBase(): string + { + return $this->baseTemplate; + } + + public function addTemplates($templates, string $basePath) + { + foreach ($templates as $key => $template) { + $this->templates[$key] = $basePath . $template; + } + } + + public static function includeTemplate($template, $suffix = '.php'): bool|string + { + $tx = self::getInstance()->getCache($template); + if ($tx === "") { + $dir = self::getInstance()->getDir(); + $tx = $dir . $template; + } + $tx .= $suffix; + if (file_exists($tx)) { + ob_start(); + include_once $tx; + return ob_get_clean(); + } + return ''; + } + + private function getCache($template) + { + return $this->templates[$template] ?? ''; + } +} \ No newline at end of file diff --git a/src/Venom/Helper/URLHelper.php b/src/Venom/Helper/URLHelper.php old mode 100644 new mode 100755 index 534632b..697b8fa --- a/src/Venom/Helper/URLHelper.php +++ b/src/Venom/Helper/URLHelper.php @@ -42,4 +42,9 @@ class URLHelper { return $url; } + + public function isAdminUrl(): bool + { + return strpos($this->parsedUrl, '/admin') === 0; + } } \ No newline at end of file diff --git a/src/Venom/Models/User.php b/src/Venom/Models/User.php deleted file mode 100644 index 43f98d8..0000000 --- a/src/Venom/Models/User.php +++ /dev/null @@ -1,14 +0,0 @@ -maxParameters = $count; + $this->routes = $config; + } + + public function getDefinitions($method, mixed $subRoute): ?array + { + if ($this->isSecure && !Security::get()->hasPermission($this->module, $method === Route::GET ? RoleEntity::TYPE_READ : RoleEntity::TYPE_WRITE)) { + return null; + } + if (isset($this->routes[$subRoute]) && isset($this->routes[$subRoute][$method])) { + return [ + "cl" => $this->controller, + "fnc" => $this->routes[$subRoute][$method] + ]; + } + return null; + } } \ No newline at end of file diff --git a/src/Venom/Routing/Router.php b/src/Venom/Routing/Router.php old mode 100644 new mode 100755 index 091d853..e5ca533 --- a/src/Venom/Routing/Router.php +++ b/src/Venom/Routing/Router.php @@ -5,9 +5,12 @@ namespace Venom\Routing; use Exception; +use Venom\Exceptions\ExceptionHandler; class Router { + public const DEFAULT_ROUTER = 'defaultRouter'; + public const ADMIN_ROUTER = 'adminRouter'; protected string $id = 'defaultRouter'; protected int $version; protected string $prefix = ''; @@ -22,10 +25,10 @@ class Router public function addRoutes(array $routes): void { - $this->routes = $routes; + $this->routes = array_merge($this->routes, $routes); } - public function addRoute(string $path, array $route): void + public function addRoute(string $path, Route $route): void { $this->routes[$path] = $route; } @@ -36,6 +39,7 @@ class Router public function findRoute($url, $method): ?array { $url = $this->removeIfFirst($url, $this->prefix); + $url = $this->removeTrailingSlash($url); // check if full match... this can easily done if the url isset select the empty! $method = strtoupper($method); $route = $this->getRouteByName($url, $method); @@ -51,26 +55,25 @@ class Router return null; } - private function removeIfFirst($rawString, $string) + private function removeIfFirst($rawString, $string): bool|string { - if ($string !== '' && 0 === strpos($rawString, $string)) { + if ($string !== '' && str_starts_with($rawString, $string)) { return substr($rawString, strlen($string)); } return $rawString; } - /* @todo implement Security Check if SecurityModule is used */ private function getRouteByName($url, $method, $subRoute = '*', $params = []): ?array { - $routeAvailable = isset($this->routes[$url]); - $subRouteFound = isset($this->routes[$url]['routes'][$subRoute]); - $methodFound = isset($this->routes[$url]['routes'][$subRoute][$method]); - if ($routeAvailable && $subRouteFound && $methodFound) { - return [ - 'cl' => $this->routes[$url]['cl'], - 'fnc' => $this->routes[$url]['routes'][$subRoute][$method], - 'params' => $params - ]; + if (isset($this->routes[$url])) { + /** @var Route $route */ + $route = $this->routes[$url]; + $sub = $route->getDefinitions($method, $subRoute); + if ($sub === null) { + return null; + } + $sub["params"] = array_reverse($params); + return $sub; } return null; } @@ -95,17 +98,32 @@ class Router public function tryFunctionCall(?array $aRoute): bool { - if ($aRoute === null || !is_callable(array($aRoute['cl'], $aRoute['fnc']))) { + if ($aRoute === null || empty($aRoute['cl']) || empty($aRoute['fnc']) || !class_exists($aRoute['cl'])) { return false; } $route = new $aRoute['cl'](); + if (!is_callable(array($route, $aRoute['fnc']))) { + return false; + } try { $fnc = $aRoute['fnc']; $params = $aRoute['params'] ?? []; $route->$fnc(...$params); return true; } catch (Exception $ex) { + ExceptionHandler::handleException($ex); return false; } } + + private function removeTrailingSlash(string $rawString): bool|string + { + $len = strlen($rawString); + return $rawString[$len - 1] === '/' ? substr($rawString, 0, strlen($rawString) - 1) : $rawString; + } + + public function getId(): string + { + return $this->id; + } } \ No newline at end of file diff --git a/src/Venom/Security/BaseLogin.php b/src/Venom/Security/BaseLogin.php old mode 100644 new mode 100755 index 1a94a5c..820bb24 --- a/src/Venom/Security/BaseLogin.php +++ b/src/Venom/Security/BaseLogin.php @@ -4,10 +4,54 @@ namespace Venom\Security; +use Venom\Core\ArgumentHandler; +use Venom\Core\Config; +use Venom\Entities\User; +use Venom\Helper\URLHelper; + /** * Class that Login stupid via Password, Username */ -class BaseLogin +class BaseLogin implements Login { + private User $user; + + public function __construct(User $user) + { + $this->user = $user; + } + + public function checkCredentials(): bool + { + $handler = ArgumentHandler::get(); + return $handler->hasPostItem('USERNAME') && $handler->hasPostItem('PASSWORD'); + } + + public function redirect(): void + { + $url = ArgumentHandler::get()->getPostItem('REDIRECT_TO', URLHelper::getInstance()->getUrl()); + if ($url === 'NO') { + echo json_encode(['message' => 'login'], JSON_THROW_ON_ERROR); + } else { + header('Location: ' . $url); + } + die(); + } + + public function login(): bool + { + $sec = Config::getInstance()->getSecurity(); + $this->user->username = (string)ArgumentHandler::get()->getPostItem('USERNAME'); + if (!$this->user->loadUser()) { + return false; + } + $secret = $sec->secret ?? 'venom'; + $hashed = hash($sec->algo ?? 'SHA256', ArgumentHandler::get()->getPostItem('PASSWORD') . $secret . $this->user->salt); + if ($this->user->password === $hashed) { + $_SESSION['userID'] = $this->user->id; + return true; + } + return false; + } } \ No newline at end of file diff --git a/src/Venom/Security/Login.php b/src/Venom/Security/Login.php old mode 100644 new mode 100755 index 853cffd..31a655e --- a/src/Venom/Security/Login.php +++ b/src/Venom/Security/Login.php @@ -4,16 +4,12 @@ namespace Venom\Security; -use Venom\Models\User; +use Venom\Entities\User; interface Login { public function __construct(User $user); - public function checkUsername(): bool; - - public function checkPassword(): bool; - public function checkCredentials(): bool; public function login(): bool; diff --git a/src/Venom/Security/Security.php b/src/Venom/Security/Security.php old mode 100644 new mode 100755 index 1585dbd..c5db118 --- a/src/Venom/Security/Security.php +++ b/src/Venom/Security/Security.php @@ -3,10 +3,22 @@ namespace Venom\Security; +use RuntimeException; +use Venom\Core\Config; +use Venom\Entities\RoleEntity; +use Venom\Entities\User; class Security { private static ?Security $instance = null; + private ?User $user; + + public function __construct() + { + $this->user = new User(); + $this->user->id = $_SESSION['userID'] ?? "-1"; + $this->user->load(); + } public static function get(): Security { @@ -16,15 +28,29 @@ class Security return self::$instance; } - /* @todo implement logic */ - public function hasRole(string $role): bool + public function hasPermission(string $module, $type = RoleEntity::TYPE_WRITE): bool { - return true; + return $this->user->hasPermission($module, $type); } - /* @todo implement logic */ - public function hasRoles(array $roles): bool + public function login(): void { - return true; + if ($this->user->isLoaded()) { + throw new RuntimeException('Try to re-login!'); + } + $sec = Config::getInstance()->getSecurity(); + $login = new $sec->securityClass($this->user); + if ($login instanceof Login) { + if (!$login->checkCredentials() || !$login->login()) { + http_response_code(401); + } + $login->redirect(); + } + } + + public function logout(): void + { + unset($_SESSION['userID']); + $this->user = new User(); } } diff --git a/src/Venom/Venom.php b/src/Venom/Venom.php index d27b073..9cb218e 100755 --- a/src/Venom/Venom.php +++ b/src/Venom/Venom.php @@ -4,9 +4,12 @@ namespace Venom; +use Venom\Admin\AdminController; +use Venom\Admin\AdminRouterInit; use Venom\Core\ArgumentHandler; use Venom\Core\Config; use Venom\Core\Module; +use Venom\Core\ModuleLoader; use Venom\Core\Registry; use Venom\Exceptions\ExceptionHandler; use Venom\Helper\ErrorHandler; @@ -27,16 +30,26 @@ class Venom { ExceptionHandler::setExceptionHandler(); $this->renderer = new VenomRenderer($this); + $this->routers[Router::ADMIN_ROUTER] = new Router(Router::ADMIN_ROUTER, 1.0, '/admin/api'); Asset::get()->setRenderer($this->renderer); } + public function inject(): void + { + $this->run(); + } + public function run(): void { $arguments = ArgumentHandler::get(); $arguments->setItem(ErrorHandler::ERROR_KEY, false); + $config = Config::getInstance(); + if ($config->isAdmin()) { + $this->initAdmin(); + } // we need to load the current controller and the current start template. // after this we can start the renderer - if (Config::getInstance()->isRouterEnabled()) { + if ($config->isRouterEnabled() || $config->isAdmin()) { $status = $this->useRouter(); if ($status['found']) { if ($status['status']) { @@ -48,18 +61,32 @@ class Venom $registry = Registry::getInstance(); $registry->getLang()->initLang(); // if site is errored then dont load via SEO - if (!$arguments->getItem(ErrorHandler::ERROR_KEY)) { + if (!$config->isAdmin() && !$arguments->getItem(ErrorHandler::ERROR_KEY)) { $registry->getSeo()->loadSite(); } $this->renderer->init($this->findController()); $this->renderer->render(); } + public function initAdmin(): void + { + $this->controllers['adminCtrl'] = AdminController::class; + AdminRouterInit::registerAdminRouters($this); + ArgumentHandler::get()->setItem('cl', 'adminCtrl'); + } + private function useRouter(): array { $url = URLHelper::getInstance()->getUrl(); + $isAdmin = Config::getInstance()->isAdmin(); /** @var Router $router */ - foreach ($this->routers as $router) { + foreach ($this->routers as $key => $router) { + if ($isAdmin && $key !== Router::ADMIN_ROUTER) { + continue; + } + if (!$isAdmin && $key === Router::ADMIN_ROUTER) { + continue; + } $route = $router->findRoute($url, $_SERVER['REQUEST_METHOD']); $status = $router->tryFunctionCall($route); if ($route !== null) { @@ -89,21 +116,33 @@ class Venom public function initModules(array $modules): void { - foreach ($modules as $key => $moduleClass) { - $module = new $moduleClass; - if ($module instanceof Module && $module->register()) { - $this->modules[$key] = $module; - } + if (Config::getInstance()->isAdmin()) { + $modules = array_merge(ModuleLoader::getModules(), $modules); + } + foreach ($modules as $module) { + ModuleLoader::loadModule($module, $this); } } - public function initControllers(array $controllers): void + public function addControllers(array $controllers): void { - $this->controllers = $controllers; + $this->controllers = array_merge($this->controllers, $controllers); } - public function addRouter(string $name, Router $router): void + public function addRouter(Router $router): void { - $this->routers[$name] = $router; + $this->routers[$router->getId()] = $router; + } + + public function getRouter(string $router): ?Router + { + return $this->routers[$router]; + } + + public function registerModule(array $module) + { + if (ModuleLoader::initModule($module, $this)) { + $this->modules[$module[Module::NAME]] = $module; + } } } \ No newline at end of file diff --git a/src/Venom/Views/Asset.php b/src/Venom/Views/Asset.php index c4c4e03..7233737 100755 --- a/src/Venom/Views/Asset.php +++ b/src/Venom/Views/Asset.php @@ -62,7 +62,7 @@ class Asset usort($this->jsFiles, function ($a, $b) { return $a['pos'] <=> $b['pos']; }); - $theme = $this->getPath('/theme/' . Config::getInstance()->getRenderer()->assetDir . '/js/'); + $theme = $this->getPath('/js/'); foreach ($this->jsFiles as $key => $file) { echo ''; } @@ -70,7 +70,8 @@ class Asset private function getPath($base): string { - $preDir = $base; + $dir = Config::getInstance()->isAdmin() ? 'admin' : Config::getInstance()->getRenderer()->assetDir; + $preDir = '/theme/' . $dir . $base; $config = Config::getInstance(); $baseUrl = Config::getInstance()->getBaseUrl(); if ($baseUrl !== '' && $config->getRenderer()->useStaticUrl) { @@ -84,7 +85,7 @@ class Asset usort($this->cssFiles, function ($a, $b) { return $a['pos'] <=> $b['pos']; }); - $theme = $this->getPath('/theme/' . Config::getInstance()->getRenderer()->assetDir . '/css/'); + $theme = $this->getPath('/css/'); foreach ($this->cssFiles as $key => $file) { echo ''; } diff --git a/src/Venom/Views/DataLoader.php b/src/Venom/Views/DataLoader.php index 90405b4..4d9bea6 100755 --- a/src/Venom/Views/DataLoader.php +++ b/src/Venom/Views/DataLoader.php @@ -5,7 +5,7 @@ namespace Venom\Views; use RuntimeException; use Venom\Core\DatabaseHandler; -use Venom\Models\DataModel; +use Venom\Entities\DataEntity; class DataLoader { @@ -23,7 +23,7 @@ class DataLoader return self::$instance; } - public static function loadById(string $id): ?DataModel + public static function loadById(string $id): ?DataEntity { if ($id === '') { throw new RuntimeException('Try to Load empty ID from Database'); @@ -33,14 +33,14 @@ class DataLoader ]); if ($data !== null) { - $model = new DataModel($data->identity, $data->datatype, $data->raw, $data->generated); + $model = new DataEntity($data->identity, $data->datatype, $data->raw, $data->generated); $model->setActive(true); return $model; } return null; } - public function updateData(DataModel $model): bool + public function updateData(DataEntity $model): bool { if ($model->getId() === '') { return $this->insertData($model); @@ -57,7 +57,7 @@ class DataLoader ); } - public function insertData(DataModel $model): bool + public function insertData(DataEntity $model): bool { $this->validateModel($model); @@ -73,7 +73,7 @@ class DataLoader ); } - private function validateModel(DataModel $model): void + private function validateModel(DataEntity $model): void { if ($model->getId() === '') { $model->setId($this->generateID()); @@ -87,8 +87,8 @@ class DataLoader private function generateID(string $id = ''): string { if ($id === '') { - $id = bin2hex(random_bytes(16)); + $id = bin2hex(random_bytes(32)); } - return $id; + return hash('SHA256', $id); } -} +} \ No newline at end of file diff --git a/src/Venom/Views/RenderController.php b/src/Venom/Views/RenderController.php old mode 100644 new mode 100755 diff --git a/src/Venom/Views/VenomRenderer.php b/src/Venom/Views/VenomRenderer.php index 5e9c51e..505c246 100755 --- a/src/Venom/Views/VenomRenderer.php +++ b/src/Venom/Views/VenomRenderer.php @@ -6,14 +6,17 @@ namespace Venom\Views; use Venom\Core\ArgumentHandler; use Venom\Core\Config; +use Venom\Helper\MetaGenerator; +use Venom\Helper\TemplateUtil; use Venom\Venom; class VenomRenderer { private Venom $venom; private ?RenderController $controller; + private ?MetaGenerator $metaGenerator; private string $templateData = ''; - private array $vars = []; + private array $vars = []; private string $baseTemplate = ''; private string $templateDir = ''; @@ -50,8 +53,7 @@ class VenomRenderer public function renderTemplate($template): void { // random variable name... to remove it instantly - echo $this->includeTemplate($template, '1408138186'); - unset($this->vars['1408138186']); + echo TemplateUtil::includeTemplate($template); } /** @@ -62,16 +64,9 @@ class VenomRenderer */ public function includeTemplate($template, $varName = '') { - $template .= '.php'; - if (file_exists($this->templateDir . $template)) { - ob_start(); - include_once $this->templateDir . $template; - $data = ob_get_clean(); - $this->vars[$varName] = $data; - return $data; - } - $this->vars[$varName] = ''; - return ''; + $data = TemplateUtil::includeTemplate($template); + $this->vars[$varName] = $data; + return $data; } public function addVar($name, $value): void @@ -84,7 +79,7 @@ class VenomRenderer return $this->vars[$name]; } - public function deleteVar($name) + public function deleteVar($name): void { unset($this->vars[$name]); } @@ -92,8 +87,12 @@ class VenomRenderer public function init(?RenderController $controller): void { $this->controller = $controller; - $data = Config::getInstance()->getRenderer(); - $this->baseTemplate = $data->baseFile . '.php' ?? 'base.php'; - $this->templateDir = __DIR__ . '/../../../tpl/' . $data->theme . '/'; + if (!Config::getInstance()->isAdmin()) { + $this->metaGenerator = new MetaGenerator(); + $this->metaGenerator->loadById(); + } + $util = TemplateUtil::getInstance(); + $this->templateDir = $util->getDir(); + $this->baseTemplate = $util->getBase(); } } diff --git a/src/modules/.gitkeep b/src/modules/.gitkeep old mode 100644 new mode 100755 diff --git a/src/modules/Data/Controller/DataController.php b/src/modules/Data/Controller/DataController.php new file mode 100644 index 0000000..4e57780 --- /dev/null +++ b/src/modules/Data/Controller/DataController.php @@ -0,0 +1,13 @@ +registerModule([ + Module::ACTIVE => true, + Module::NAME => 'DataModule', + Module::DESC => 'Data Module for Content every', + Module::AUTHOR => 'VstZ dev', + // NEED TO CHECK RIGHTS? :D IF FALSE WRITE IS ALWAYS ALLOWED ALSO READ! + Module::SECURE => true, + Module::ROUTE => [], + Module::ADMIN_ROUTE => [ + '/data' => new Route(DataController::class, [ + "*" => [ + Route::GET => 'get' + ] + ]) + ], + Module::TEMPLATE_PATH => __DIR__ . "/tpl/", + Module::TEMPLATES => [ + + ], + Module::ADMIN_TEMPLATES => [ + + ], + Module::CONTROLLER => [ + + ] +]); \ No newline at end of file diff --git a/src/modules/Meta/Controller/MetaAPIController.php b/src/modules/Meta/Controller/MetaAPIController.php new file mode 100755 index 0000000..9491a7d --- /dev/null +++ b/src/modules/Meta/Controller/MetaAPIController.php @@ -0,0 +1,36 @@ +registerModule([ + Module::ACTIVE => true, + Module::NAME => 'MetaModule', + Module::DESC => 'Meta Data Module for SEO', + Module::AUTHOR => 'VstZ dev', + // NEED TO CHECK RIGHTS? :D IF FALSE WRITE IS ALWAYS ALLOWED ALSO READ! + Module::SECURE => true, + Module::ROUTE => [], + Module::ADMIN_ROUTE => [ + '/metaData' => new Route(MetaAPIController::class, [ + "*" => [ + Route::GET => 'get' + ], + "1" => [ + Route::GET => 'getById', + Route::POST => 'update', + Route::PUT => 'insert', + Route::DELETE => 'delete' + ] + ]) + ], + Module::TEMPLATE_PATH => __DIR__ . "/tpl/", + Module::TEMPLATES => [ + // Include Templates with shorter names! $render->include("meta_roles") + //'meta_roles' => 'PATH_TO_TEMPLATE_FROM_TEMPLATE_PATH' + ], + Module::ADMIN_TEMPLATES => [ + // + ], + Module::CONTROLLER => [ + + ] +]); \ No newline at end of file diff --git a/src/modules/Role/Controller/RoleController.php b/src/modules/Role/Controller/RoleController.php new file mode 100644 index 0000000..0890f38 --- /dev/null +++ b/src/modules/Role/Controller/RoleController.php @@ -0,0 +1,21 @@ + [ + ['id' => 1, 'name' => 'Admin', 'icon' => 'vt-visibility'], + ['id' => 2, 'name' => 'Moderator', 'icon' => 'vt-edit'], + ['id' => 3, 'name' => 'Gast', 'icon' => 'vt-edit'], + ] + ]); + } +} \ No newline at end of file diff --git a/src/modules/Role/module.php b/src/modules/Role/module.php new file mode 100755 index 0000000..115fc8c --- /dev/null +++ b/src/modules/Role/module.php @@ -0,0 +1,34 @@ +registerModule([ + Module::ACTIVE => true, + Module::NAME => 'RoleModule', + Module::DESC => 'Role Management', + Module::AUTHOR => 'VstZ dev', + // NEED TO CHECK RIGHTS? :D IF FALSE WRITE IS ALWAYS ALLOWED ALSO READ! + Module::SECURE => true, + Module::ROUTE => [], + Module::ADMIN_ROUTE => [ + '/roles' => new Route(RoleController::class, [ + "*" => [ + Route::GET => 'get' + ] + ]) + ], + Module::TEMPLATE_PATH => __DIR__ . "/tpl/", + Module::TEMPLATES => [ + + ], + Module::ADMIN_TEMPLATES => [ + + ], + Module::CONTROLLER => [ + + ] +]); \ No newline at end of file diff --git a/src/modules/SEO/Controller/SeoUrlController.php b/src/modules/SEO/Controller/SeoUrlController.php new file mode 100755 index 0000000..eb1c6d8 --- /dev/null +++ b/src/modules/SEO/Controller/SeoUrlController.php @@ -0,0 +1,18 @@ +registerModule([ + Module::ACTIVE => true, + Module::NAME => 'SeoModule', + Module::DESC => 'SEO Management for beautiful URLs', + Module::AUTHOR => 'VstZ dev', + // NEED TO CHECK RIGHTS? :D IF FALSE WRITE IS ALWAYS ALLOWED ALSO READ! + Module::SECURE => true, + Module::ROUTE => [], + Module::ADMIN_ROUTE => [ + '/seoUrl' => new Route(SeoUrlController::class, [ + "*" => [ + Route::GET => 'get' + ], + "1" => [ + Route::GET => 'getById', + Route::POST => 'update', + Route::PUT => 'insert', + Route::DELETE => 'delete' + ] + ]) + ], + Module::TEMPLATE_PATH => __DIR__ . "/tpl/", + Module::TEMPLATES => [ + + ], + Module::ADMIN_TEMPLATES => [ + + ], + Module::CONTROLLER => [ + + ] +]); \ No newline at end of file diff --git a/src/modules/User/Controller/UserAPIController.php b/src/modules/User/Controller/UserAPIController.php new file mode 100755 index 0000000..a4c7000 --- /dev/null +++ b/src/modules/User/Controller/UserAPIController.php @@ -0,0 +1,57 @@ +load($easyQuery); + //['id' => 1, 'name' => 'engineertrooper', 'icon' => 'vt-edit'], + AdminHelper::sendResponse(["users" => $entityManager->getAll()]); + } + + public function getById($id) + { + $entityManager = EntityManager::create(User::class); + $easyQuery = new EasyQuery(User::$tableName, ["id", "username", "firstname", "lastname", "email", "isActive"]); + $easyQuery->where("id", $id); + $entityManager->load($easyQuery); + AdminHelper::sendResponse($entityManager->getFirst()); + } + + public function update($id) + { + $entityManager = EntityManager::create(User::class); + $easyQuery = new EasyQuery(User::$tableName, ["id", "username", "firstname", "lastname", "email", "isActive"]); + $easyQuery->where("id", $id); + $entityManager->load($easyQuery); + /** @var User|null $original */ + $original = $entityManager->getFirst(); + if ($original == null) { + AdminHelper::sendStatus(false, "User not Found"); + } + parse_str(file_get_contents('php://input'), $_PUT); +/* var_dump(array_keys($_PUT));*/ + AdminHelper::sendStatus($entityManager->saveAll()); + } + + public function delete($id) + { + } + + public function create($id) + { + // INSERT INTO + AdminHelper::sendStatus(true); + } +} \ No newline at end of file diff --git a/src/modules/User/module.php b/src/modules/User/module.php new file mode 100755 index 0000000..3f8d42a --- /dev/null +++ b/src/modules/User/module.php @@ -0,0 +1,42 @@ +registerModule([ + Module::ACTIVE => true, + Module::NAME => 'UserModule', + Module::DESC => 'User Management', + Module::AUTHOR => 'VstZ dev', + // NEED TO CHECK RIGHTS? :D IF FALSE WRITE IS ALWAYS ALLOWED ALSO READ! + Module::SECURE => true, + Module::ROUTE => [], + Module::ADMIN_ROUTE => [ + '/users' => new Route(UserAPIController::class, [ + "*" => [ + Route::GET => 'get' + ], + "1" => [ + Route::GET => 'getById', + Route::POST => 'update', + Route::PUT => 'update', + Route::DELETE => 'delete' + ] + ] + ) + ], + Module::TEMPLATE_PATH => __DIR__ . "/tpl/", + Module::TEMPLATES => [ + ], + Module::ADMIN_TEMPLATES => [ + // + ], + Module::CONTROLLER => [ + + ] +]); \ No newline at end of file diff --git a/src/modules/VenomStatus/Controller/VenomStatusController.php b/src/modules/VenomStatus/Controller/VenomStatusController.php new file mode 100644 index 0000000..273bb71 --- /dev/null +++ b/src/modules/VenomStatus/Controller/VenomStatusController.php @@ -0,0 +1,13 @@ +registerModule([ + Module::ACTIVE => true, + Module::NAME => 'VenomStatusModule', + Module::DESC => 'Show Routes and Modules!', + Module::AUTHOR => 'VstZ dev', + // NEED TO CHECK RIGHTS? :D IF FALSE WRITE IS ALWAYS ALLOWED ALSO READ! + Module::SECURE => true, + Module::ROUTE => [], + Module::ADMIN_ROUTE => [ + '/venomStatus' => new Route(VenomStatusController::class, [ + "*" => [ + Route::GET => 'get' + ] + ]) + ], + Module::TEMPLATE_PATH => __DIR__ . "/tpl/", + Module::TEMPLATES => [ + + ], + Module::ADMIN_TEMPLATES => [ + + ], + Module::CONTROLLER => [ + + ] +]); \ No newline at end of file diff --git a/tpl/.gitkeep b/tpl/.gitkeep old mode 100644 new mode 100755 diff --git a/tpl/admin/admin-panel.php b/tpl/admin/admin-panel.php new file mode 100755 index 0000000..8feb1c2 --- /dev/null +++ b/tpl/admin/admin-panel.php @@ -0,0 +1,22 @@ +
+ +
+ +
+
+
+
+ + + +
+
\ No newline at end of file diff --git a/tpl/admin/base.php b/tpl/admin/base.php new file mode 100755 index 0000000..993f708 --- /dev/null +++ b/tpl/admin/base.php @@ -0,0 +1,28 @@ + + + + + + + + Venom Admin Interface + renderCSS(); ?> + + +isDevMode() ? 'debug' : ''?>> +getVar('isLoggedIn')) { + $this->renderTemplate('login'); +} else { + $this->renderTemplate('admin-panel'); +} +Asset::get()->renderJS(); +?> + + diff --git a/tpl/admin/jsTemplates/includes/btn.tpl b/tpl/admin/jsTemplates/includes/btn.tpl new file mode 100755 index 0000000..5f86c0d --- /dev/null +++ b/tpl/admin/jsTemplates/includes/btn.tpl @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/input.tpl b/tpl/admin/jsTemplates/includes/input.tpl new file mode 100755 index 0000000..9ae7ad0 --- /dev/null +++ b/tpl/admin/jsTemplates/includes/input.tpl @@ -0,0 +1,9 @@ +${default} + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/select.tpl b/tpl/admin/jsTemplates/includes/select.tpl new file mode 100755 index 0000000..b7b55c9 --- /dev/null +++ b/tpl/admin/jsTemplates/includes/select.tpl @@ -0,0 +1,8 @@ + + + + {foreach(object as item)} + ${item.name} + {/for} + + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/svg.tpl b/tpl/admin/jsTemplates/includes/svg.tpl new file mode 100755 index 0000000..cbf6d06 --- /dev/null +++ b/tpl/admin/jsTemplates/includes/svg.tpl @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/switch.tpl b/tpl/admin/jsTemplates/includes/switch.tpl new file mode 100755 index 0000000..ed625ab --- /dev/null +++ b/tpl/admin/jsTemplates/includes/switch.tpl @@ -0,0 +1,5 @@ + + + + ${desc} + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/metaDataList.tpl b/tpl/admin/jsTemplates/metaDataList.tpl new file mode 100755 index 0000000..c77664e --- /dev/null +++ b/tpl/admin/jsTemplates/metaDataList.tpl @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/overview.tpl b/tpl/admin/jsTemplates/overview.tpl new file mode 100755 index 0000000..d12220f --- /dev/null +++ b/tpl/admin/jsTemplates/overview.tpl @@ -0,0 +1,3 @@ +
+

Overview

+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/pageEdit.tpl b/tpl/admin/jsTemplates/pageEdit.tpl new file mode 100755 index 0000000..8d8eed7 --- /dev/null +++ b/tpl/admin/jsTemplates/pageEdit.tpl @@ -0,0 +1,35 @@ +
+
+

Page Edit: Turbinen sind geil

+
+
+ + {include(includes/svg;class=back-arrow;icon=vt-arrow-back)} + +
+ +
+

Page Information

+ {include(includes/input;class=input-group;label=Page Name;name=PageName;error=Page Name is required;default=Turbinen sind geil)} +
+
+ {include(includes/select;name=pageVisibility;label=Current Author;object=$users)} +
+
+ + + + Visible + Privat + + +
+
+ ! +
+
+ {include(includes/btn;type=valid;content=Save)} + {include(includes/btn;type=primary;content=Reset)} + {include(includes/btn;type=warn;content=Delete Page)} +
+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/pagesList.tpl b/tpl/admin/jsTemplates/pagesList.tpl new file mode 100755 index 0000000..29509ad --- /dev/null +++ b/tpl/admin/jsTemplates/pagesList.tpl @@ -0,0 +1,22 @@ +
+
+

Pages

+
+
+

Add new Page

+ {include(includes/input;label=New Page Name;name=newPageName;error=New Page Name is required;type=text)} + {include(includes/btn;type=primary;content=Add)} +
+
+

All Pages

+ {foreach(pages as page,key)} +
+ + {include(includes/svg;icon=$page.icon)} + + ${page.name} +
+ {/for} +
+
+ diff --git a/tpl/admin/jsTemplates/roleEdit.tpl b/tpl/admin/jsTemplates/roleEdit.tpl new file mode 100755 index 0000000..7eb73fd --- /dev/null +++ b/tpl/admin/jsTemplates/roleEdit.tpl @@ -0,0 +1,59 @@ +
+
+

Role: ${roles.name}

+
+ + {include(includes/svg;class=back-arrow;icon=vt-arrow-back)} + +
+ {include(includes/switch;id=${switch.id};name=permissionEditMetaData;desc=Active)} + {include(includes/input;class=input-group;label=Name;name=roleName;error=Name is required;default=$roles.name;classes=spacer)} +
+ +

Privileges

+ + + Module + Edit + View + + + Meta-Data + {include(includes/switch;id=${switch.id};name=permissionEditMetaData)} + {include(includes/switch;id=${switch.id};name=permissionViewMetaData)} + + + Pages + {include(includes/switch;id=${switch.id};name=permissionEditPages)} + {include(includes/switch;id=${switch.id};name=permissionViewPages)} + + + Roles + {include(includes/switch;id=${switch.id};name=permissionEditRoles)} + {include(includes/switch;id=${switch.id};name=permissionViewRoles)} + + + SEO-URL + {include(includes/switch;id=${switch.id};name=permissionEditSeoUrl)} + {include(includes/switch;id=${switch.id};name=permissionViewSeoUrl)} + + + Users + {include(includes/switch;id=${switch.id};name=permissionEditUsers)} + {include(includes/switch;id=${switch.id};name=permissionViewUsers)} + + + VENOM-Status + {include(includes/switch;id=${switch.id};name=permissionEditVenomStatus)} + {include(includes/switch;id=${switch.id};name=permissionViewVenomStatus)} + + +
+
+
+ {include(includes/btn;type=valid;content=Save)} + {include(includes/btn;type=primary;content=Reset)} + {include(includes/btn;type=warn;content=Delete Role)} +
+
+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/rolesList.tpl b/tpl/admin/jsTemplates/rolesList.tpl new file mode 100755 index 0000000..b0d7c18 --- /dev/null +++ b/tpl/admin/jsTemplates/rolesList.tpl @@ -0,0 +1,23 @@ +
+
+

Roles

+
+
+
+

Overview

+ {foreach(roles as role,key)} +
+ + {include(includes/svg;icon=$role.icon)} + + ${role.name} +
+ {/for} +
+
+

Add new Role

+ {include(includes/input;label=New Role Name;name=newRoleName;error=New Role Name is required;type=text)} + {include(includes/btn;type=primary;content=Add)} +
+
+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/seoUrlEdit.tpl b/tpl/admin/jsTemplates/seoUrlEdit.tpl new file mode 100755 index 0000000..8380bbb --- /dev/null +++ b/tpl/admin/jsTemplates/seoUrlEdit.tpl @@ -0,0 +1,10 @@ +
+
+

SEO Urls Edit

+
+
+ + {include(includes/svg;class=back-arrow;icon=vt-arrow-back)} + +
+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/seoUrlList.tpl b/tpl/admin/jsTemplates/seoUrlList.tpl new file mode 100755 index 0000000..857cd51 --- /dev/null +++ b/tpl/admin/jsTemplates/seoUrlList.tpl @@ -0,0 +1,5 @@ +
+
+

SEO Urls

+
+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/userEdit.tpl b/tpl/admin/jsTemplates/userEdit.tpl new file mode 100755 index 0000000..7f4a1f0 --- /dev/null +++ b/tpl/admin/jsTemplates/userEdit.tpl @@ -0,0 +1,34 @@ +
+
+

User: ${username}

+
+
+ + {include(includes/svg;class=back-arrow;icon=vt-arrow-back)} + +
+ +

User-Data

+
+
+ {include(includes/input;class=input-group;label=Username;name=username;error=Username is required;default=$username)} + {include(includes/input;class=input-group;label=Firstname;name=firstname;error=Firstname is required;default=$firstname;classes=spacer)} + {include(includes/input;class=input-group;label=Lastname;name=lastname;error=Lastname is required;default=$lastname;classes=spacer)} + {include(includes/input;class=input-group;label=E-Mail;name=newEMailAddress;error=E-Mail Address is required;default=$email;classes=spacer)} + +
+
+ {include(includes/btn;type=valid;content=Save)} + {include(includes/btn;type=warn;content=Delete User)} +
+
+ +

Change Password

+
+ {include(includes/input;class=input-group;label=Password;name=password;type=password;classes=spacer)} + {include(includes/input;class=input-group;label=Password (Repeat);name=passwordRepeat;type=password;classes=spacer)} +
+ {include(includes/btn;type=valid;content=Save)} +
+
+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/usersList.tpl b/tpl/admin/jsTemplates/usersList.tpl new file mode 100755 index 0000000..1781db8 --- /dev/null +++ b/tpl/admin/jsTemplates/usersList.tpl @@ -0,0 +1,23 @@ +
+
+

Users

+
+
+
+
+
+

Overview

+ {foreach(users as user)} +
+ + {include(includes/svg;icon=vt-edit)} + + ${user.username} (${user.firstname} ${user.lastname}) +
+ {/for} +
+
+ {include(includes/btn;type=primary;content=Add User)} +
+
+
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/venomStatus.tpl b/tpl/admin/jsTemplates/venomStatus.tpl new file mode 100755 index 0000000..23acf36 --- /dev/null +++ b/tpl/admin/jsTemplates/venomStatus.tpl @@ -0,0 +1,5 @@ +
+
+

Venom Status

+
+
\ No newline at end of file diff --git a/tpl/admin/login.php b/tpl/admin/login.php new file mode 100755 index 0000000..00c2321 --- /dev/null +++ b/tpl/admin/login.php @@ -0,0 +1,28 @@ + +
+
+ +

Login Failed

+ + + + + + + Forgotten Password? [not active!] +
+
diff --git a/tpl/default/base.php b/tpl/default/base.php old mode 100644 new mode 100755