├── .travis.yml ├── public └── uploads │ └── .gitkeep ├── index.php ├── .gitignore ├── cache └── .gitignore ├── logs └── .gitignore ├── tmp └── .gitignore ├── modules ├── index │ ├── routes.php │ └── Views │ │ └── Main.php ├── maintenance │ ├── routes.php │ └── Main.php ├── wizard │ ├── routes.php │ ├── Views │ │ └── Main.php │ ├── Classes │ │ ├── TestMySQLDB.php │ │ ├── TestPostgreDB.php │ │ ├── Config.php │ │ ├── TestSQLiteDB.php │ │ ├── InstallPGSQLDB.php │ │ └── InstallSQLiteDB.php │ ├── Controllers │ │ ├── Test.php │ │ └── Install.php │ └── languages │ │ ├── en.lng │ │ └── it.lng └── admin │ ├── Models │ ├── Permission.php │ ├── Category.php │ ├── BaseModel.php │ ├── Media.php │ ├── Role.php │ └── MLogin.php │ ├── Controllers │ ├── LoginViewController.php │ ├── DashboardController.php │ ├── CLogout.php │ └── CLogin.php │ ├── Views │ └── Main.php │ ├── Helpers │ ├── AuthHelper.php │ └── JWTHelper.php │ ├── Middleware │ ├── PermissionMiddleware.php │ └── AuthMiddleware.php │ └── routes.php ├── templates ├── default │ ├── css │ │ ├── flags.png │ │ ├── style.css │ │ ├── color │ │ │ └── default.css │ │ ├── flags.css │ │ ├── 404.css │ │ ├── 500.css │ │ └── loader.css │ ├── img │ │ ├── bg-mac.png │ │ ├── perseus.png │ │ └── achilles.png │ ├── fonts │ │ ├── poppins │ │ │ ├── Poppins-Bold.ttf │ │ │ ├── Poppins-Thin.ttf │ │ │ ├── Poppins-Black.ttf │ │ │ ├── Poppins-Italic.ttf │ │ │ ├── Poppins-Light.ttf │ │ │ ├── Poppins-Medium.ttf │ │ │ ├── Poppins-ExtraBold.ttf │ │ │ ├── Poppins-Regular.ttf │ │ │ ├── Poppins-SemiBold.ttf │ │ │ ├── Poppins-BlackItalic.ttf │ │ │ ├── Poppins-BoldItalic.ttf │ │ │ ├── Poppins-ExtraLight.ttf │ │ │ ├── Poppins-LightItalic.ttf │ │ │ ├── Poppins-ThinItalic.ttf │ │ │ ├── Poppins-MediumItalic.ttf │ │ │ ├── Poppins-SemiBoldItalic.ttf │ │ │ ├── Poppins-ExtraBoldItalic.ttf │ │ │ └── Poppins-ExtraLightItalic.ttf │ │ ├── font-awesome-4.7.0 │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ │ ├── less │ │ │ │ ├── fixed-width.less │ │ │ │ ├── screen-reader.less │ │ │ │ ├── larger.less │ │ │ │ ├── list.less │ │ │ │ ├── core.less │ │ │ │ ├── stacked.less │ │ │ │ ├── font-awesome.less │ │ │ │ ├── bordered-pulled.less │ │ │ │ ├── rotated-flipped.less │ │ │ │ ├── path.less │ │ │ │ ├── animated.less │ │ │ │ └── mixins.less │ │ │ ├── scss │ │ │ │ ├── _fixed-width.scss │ │ │ │ ├── _screen-reader.scss │ │ │ │ ├── _larger.scss │ │ │ │ ├── _list.scss │ │ │ │ ├── _core.scss │ │ │ │ ├── font-awesome.scss │ │ │ │ ├── _stacked.scss │ │ │ │ ├── _bordered-pulled.scss │ │ │ │ ├── _rotated-flipped.scss │ │ │ │ ├── _path.scss │ │ │ │ ├── _animated.scss │ │ │ │ └── _mixins.scss │ │ │ └── HELP-US-OUT.txt │ │ └── Linearicons-Free-v1.0.0 │ │ │ └── WebFont │ │ │ ├── Linearicons-Free.eot │ │ │ ├── Linearicons-Free.ttf │ │ │ ├── Linearicons-Free.woff │ │ │ └── Linearicons-Free.woff2 │ ├── js │ │ ├── 500.js │ │ ├── functions.js │ │ ├── jquery.jquery-password-generator-plugin.min.js │ │ ├── editorjs │ │ │ ├── delimiter.umd.js │ │ │ ├── code.umd.js │ │ │ ├── delimiter.mjs │ │ │ ├── warning.umd.js │ │ │ ├── toolbox.d.ts │ │ │ └── index.d.ts │ │ └── 404.js │ ├── maintenance.twig │ ├── index.twig │ ├── 500.twig │ └── wizard_charset.json └── admin │ ├── login.twig │ ├── users │ ├── edit.twig │ └── create.twig │ └── permissions │ ├── create.twig │ └── edit.twig ├── core └── perseo │ ├── DB.php │ ├── Translator.php │ ├── MiddleWare │ ├── DefaultErrorRender.php │ ├── Admin.php │ ├── Wizard.php │ ├── Maintenance.php │ ├── ErrorHandlerMiddleware.php │ └── Alias.php │ ├── LoggerFactory.php │ └── Handlers │ ├── ShutdownHandler.php │ └── HttpErrorHandler.php ├── config ├── routes.php ├── middleware.php ├── default.php └── bootstrap.php ├── composer.json └── .htaccess /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 7.4 4 | -------------------------------------------------------------------------------- /public/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | # This directory is used for media uploads 2 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | run(); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /config/settings.php 3 | /composer.lock 4 | .DS_Store 5 | /.idea 6 | *.iml -------------------------------------------------------------------------------- /cache/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /tmp/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /modules/index/routes.php: -------------------------------------------------------------------------------- 1 | get('/', \Modules\index\Views\Main::class); -------------------------------------------------------------------------------- /templates/default/css/flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/css/flags.png -------------------------------------------------------------------------------- /templates/default/img/bg-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/img/bg-mac.png -------------------------------------------------------------------------------- /templates/default/img/perseus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/img/perseus.png -------------------------------------------------------------------------------- /templates/default/img/achilles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/img/achilles.png -------------------------------------------------------------------------------- /modules/maintenance/routes.php: -------------------------------------------------------------------------------- 1 | get('/maintenance', \Modules\maintenance\Main::class); -------------------------------------------------------------------------------- /templates/default/css/style.css: -------------------------------------------------------------------------------- 1 | .input-group { width: 100%; } 2 | .input-group-addon { 3 | width:200px; 4 | text-align:left; 5 | } 6 | -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-Bold.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-Thin.ttf -------------------------------------------------------------------------------- /templates/default/js/500.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | setTimeout(function(){ 3 | $('body').removeClass('loading'); 4 | }, 1000); 5 | }); 6 | -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-Black.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-Italic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-Light.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-Medium.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-Regular.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/poppins/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/poppins/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.eot -------------------------------------------------------------------------------- /templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.ttf -------------------------------------------------------------------------------- /templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.woff -------------------------------------------------------------------------------- /templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/per-seo/cms/HEAD/templates/default/fonts/Linearicons-Free-v1.0.0/WebFont/Linearicons-Free.woff2 -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /templates/default/js/functions.js: -------------------------------------------------------------------------------- 1 | function makerand(possible, count) { 2 | var text = ""; 3 | 4 | for (var i = 0; i < count; i++) 5 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 6 | 7 | return text; 8 | } -------------------------------------------------------------------------------- /core/perseo/DB.php: -------------------------------------------------------------------------------- 1 | get('/wizard[/]', \Modules\wizard\Views\Main::class); 6 | $app->post('/wizard/test[/]', \Modules\wizard\Controllers\Test::class); 7 | $app->post('/wizard/install[/]', \Modules\wizard\Controllers\Install::class); -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /modules/admin/Models/Permission.php: -------------------------------------------------------------------------------- 1 | db->get($this->table, '*', ['slug' => $slug]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /templates/default/js/jquery.jquery-password-generator-plugin.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-password-generator-plugin - v0.0.0 - 2015-10-23 2 | * Copyright (c) 2015 Sergey Sokurenko; Licensed MIT */ 3 | !function(a){a.passGen=function(b){b=a.extend({},a.passGen.options,b);var c,d,e="",f="";c={numeric:"0123456789",lowercase:"abcdefghijklmnopqrstuvwxyz",uppercase:"ABCDEFGHIJKLMNOPQRSTUVWXYZ",special:"~!@#$%^&*()-+[]{}<>?"},a.each(c,function(a,c){b[a]&&(e+=c)});for(var g=0;glanguage = strtolower($language); 14 | $this->path = $path . DIRECTORY_SEPARATOR .'languages'. DIRECTORY_SEPARATOR; 15 | $this->result = array(); 16 | if (file_exists($this->path . $this->language . '.lng')) { 17 | $content = file_get_contents($this->path . $this->language . '.lng'); 18 | $this->result = json_decode($content, true); 19 | } 20 | } 21 | 22 | public function get() 23 | { 24 | return $this->result; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /modules/maintenance/Main.php: -------------------------------------------------------------------------------- 1 | app = $app; 20 | $this->container = $container; 21 | $this->twig = $twig; 22 | } 23 | 24 | public function __invoke(Request $request, Response $response): Response { 25 | $viewData = [ 26 | 'basepath' => (string) $this->app->getBasePath(), 27 | ]; 28 | return $this->twig->render($response, 'maintenance.twig', $viewData); 29 | } 30 | } -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modules/admin/Controllers/LoginViewController.php: -------------------------------------------------------------------------------- 1 | isAuthenticated()) { 14 | return $this->redirect($response, $this->app->getBasePath() . '/admin/dashboard'); 15 | } 16 | 17 | return $this->render($response, 'login.twig', [ 18 | 'pageTitle' => 'Login' 19 | ]); 20 | } 21 | 22 | public function checkAuth(Request $request, Response $response): Response 23 | { 24 | return $this->json($response, [ 25 | 'authenticated' => $this->isAuthenticated(), 26 | 'user' => $this->getAuthUser() 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /config/routes.php: -------------------------------------------------------------------------------- 1 | getContainer(); 9 | $directory = $thiscontainer->get('settings_modules'); 10 | $dirobj = new \DirectoryIterator($directory); 11 | $modules = array(); 12 | $curmod = 0; 13 | foreach ($dirobj as $fileinfo) { 14 | if (!$fileinfo->isDot()) { 15 | $menu = $fileinfo->getPathname() . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'menu.json'; 16 | $routes = $fileinfo->getPathname() . DIRECTORY_SEPARATOR . 'routes.php'; 17 | $modules[$curmod]['name'] = $fileinfo->getBasename(); 18 | if (file_exists($menu)) { 19 | $currfile = file_get_contents($menu); 20 | $modules[$curmod]['menu'] = json_decode($currfile, true); 21 | } 22 | if (file_exists($routes)) { 23 | @include_once($routes); 24 | } 25 | $curmod++; 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /modules/admin/Views/Main.php: -------------------------------------------------------------------------------- 1 | app = $app; 22 | $this->container = $container; 23 | $this->twig = $twig; 24 | $this->settings = ($container->has('settings_global') ? $container->get('settings_global') : ['template' => 'default']); 25 | $this->template = $this->settings['template']; 26 | } 27 | 28 | public function __invoke(Request $request, Response $response): Response { 29 | $viewData = [ 30 | 'basepath' => (string) $this->app->getBasePath(), 31 | 'template' => $this->template 32 | ]; 33 | return $this->twig->render($response, $this->template . DIRECTORY_SEPARATOR .'index.twig', $viewData); 34 | } 35 | } -------------------------------------------------------------------------------- /modules/index/Views/Main.php: -------------------------------------------------------------------------------- 1 | app = $app; 22 | $this->container = $container; 23 | $this->twig = $twig; 24 | $this->settings = ($container->has('settings_global') ? $container->get('settings_global') : ['template' => 'default']); 25 | $this->template = $this->settings['template']; 26 | } 27 | 28 | public function __invoke(Request $request, Response $response): Response { 29 | $viewData = [ 30 | 'basepath' => (string) $this->app->getBasePath(), 31 | 'template' => $this->template 32 | ]; 33 | return $this->twig->render($response, $this->template . DIRECTORY_SEPARATOR .'index.twig', $viewData); 34 | } 35 | } -------------------------------------------------------------------------------- /templates/default/maintenance.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to PerSeo 2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |

 

21 |

 

22 |

 

23 |

 

24 |

We will back Soon.

25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /templates/default/index.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to PerSeo 2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |

PerSeo 2.0

21 |
22 |

Simple, Optimized CMS/FrameWork for SEO

23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/default/500.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

500

10 | {% if debug == true %} 11 |

Codice: {{ code }}
Messaggio: {{ message }}
File: {{ file }}
Riga: {{ line }}
Trace: {{ trace }}

12 | {% else %} 13 |

Errore imprevisto, torna alla home.

14 | {% endif %} 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /modules/admin/Models/Category.php: -------------------------------------------------------------------------------- 1 | db->get($this->table, '*', ['slug' => $slug]); 19 | } 20 | 21 | public function getChildren($parentId) 22 | { 23 | return $this->all('*', [ 24 | 'parent_id' => $parentId, 25 | 'ORDER' => ['name' => 'ASC'] 26 | ]); 27 | } 28 | 29 | public function getAllHierarchical() 30 | { 31 | $categories = $this->all('*', [ 32 | 'ORDER' => ['name' => 'ASC'] 33 | ]); 34 | 35 | $tree = []; 36 | $lookup = []; 37 | 38 | foreach ($categories as $category) { 39 | $category['children'] = []; 40 | $lookup[$category['id']] = $category; 41 | } 42 | 43 | foreach ($lookup as $id => $category) { 44 | if ($category['parent_id'] === null) { 45 | $tree[] = &$lookup[$id]; 46 | } else { 47 | if (isset($lookup[$category['parent_id']])) { 48 | $lookup[$category['parent_id']]['children'][] = &$lookup[$id]; 49 | } 50 | } 51 | } 52 | 53 | return $tree; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /templates/default/js/editorjs/delimiter.umd.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode('.ce-delimiter{line-height:1.6em;width:100%;text-align:center}.ce-delimiter:before{display:inline-block;content:"***";font-size:30px;line-height:65px;height:30px;letter-spacing:.2em}')),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})(); 2 | (function(t,i){typeof exports=="object"&&typeof module<"u"?module.exports=i():typeof define=="function"&&define.amd?define(i):(t=typeof globalThis<"u"?globalThis:t||self,t.Delimiter=i())})(this,function(){"use strict";const t='';/** 3 | * Delimiter Block for the Editor.js. 4 | * 5 | * @author CodeX (team@ifmo.su) 6 | * @copyright CodeX 2018 7 | * @license The MIT License (MIT) 8 | * @version 2.0.0 9 | */class i{static get isReadOnlySupported(){return!0}static get contentless(){return!0}constructor({data:e,config:s,api:r}){this.api=r,this._CSS={block:this.api.styles.block,wrapper:"ce-delimiter"},this._element=this.drawView(),this.data=e}drawView(){let e=document.createElement("div");return e.classList.add(this._CSS.wrapper,this._CSS.block),e}render(){return this._element}save(e){return{}}static get toolbox(){return{icon:t,title:"Delimiter"}}static get pasteConfig(){return{tags:["HR"]}}onPaste(e){this.data={}}}return i}); 10 | -------------------------------------------------------------------------------- /core/perseo/MiddleWare/DefaultErrorRender.php: -------------------------------------------------------------------------------- 1 | app = $app; 25 | $this->logger = $logger; 26 | $this->twig = $twig; 27 | $this->settings = ($container->has('settings_global') ? $container->get('settings_global') : ['template' => 'default']); 28 | $this->template = $this->settings['template']; 29 | } 30 | 31 | public function __invoke(Throwable $exception, bool $displayErrorDetails): string 32 | { 33 | $this->logger->error($exception); 34 | $viewData = [ 35 | 'debug' => $displayErrorDetails, 36 | 'code' => $exception->getCode(), 37 | 'message' => $exception->getMessage(), 38 | 'file' => $exception->getFile(), 39 | 'line' => $exception->getLine(), 40 | 'trace' => $exception->getTraceAsString(), 41 | 'basepath' => (string) $this->app->getBasePath(), 42 | 'template' => $this->template 43 | ]; 44 | return (string) $this->twig->fetch($this->template . DIRECTORY_SEPARATOR .'500.twig', $viewData); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/admin/Controllers/DashboardController.php: -------------------------------------------------------------------------------- 1 | db); 16 | $pageModel = new Page($this->db); 17 | $userModel = new User($this->db); 18 | 19 | $stats = [ 20 | 'totalPosts' => $postModel->count(), 21 | 'publishedPosts' => $postModel->count(['status' => 'published']), 22 | 'draftPosts' => $postModel->count(['status' => 'draft']), 23 | 'totalPages' => $pageModel->count(), 24 | 'publishedPages' => $pageModel->count(['status' => 'published']), 25 | 'draftPages' => $pageModel->count(['status' => 'draft']), 26 | 'totalUsers' => $userModel->count(), 27 | 'activeUsers' => $userModel->count(['status' => 'active']) 28 | ]; 29 | 30 | // Recent posts 31 | $recentPosts = $postModel->getAllWithAuthor(1, 5); 32 | 33 | // Recent pages 34 | $recentPages = $pageModel->getAllWithAuthor(1, 5); 35 | 36 | return $this->render($response, 'dashboard.twig', [ 37 | 'pageTitle' => 'Dashboard', 38 | 'stats' => $stats, 39 | 'recentPosts' => $recentPosts['data'], 40 | 'recentPages' => $recentPages['data'] 41 | ], $request); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /config/middleware.php: -------------------------------------------------------------------------------- 1 | addBodyParsingMiddleware(); 24 | 25 | $app->add(TwigMiddleware::class); 26 | 27 | // Add the Slim built-in routing middleware 28 | $app->addRoutingMiddleware(); 29 | 30 | $app->add(Admin::class); 31 | 32 | // Add locale in url Middleware 33 | $app->add(Locale::class); 34 | 35 | $app->add(Alias::class); 36 | 37 | $app->add(Maintenance::class); 38 | 39 | $app->add(Wizard::class); 40 | 41 | // Set language from browser 42 | $app->add(Language::class); 43 | 44 | // Session 45 | $app->add(SessionStartMiddleware::class); 46 | 47 | //Add Basepath Middleware 48 | $app->add(BasePathMiddleware::class); 49 | 50 | $app->add(HttpExceptionMiddleware::class); 51 | 52 | $app->add(ErrorHandlerMiddleware::class); 53 | 54 | // Catch exceptions and errors 55 | $app->add(ErrorMiddleware::class); 56 | 57 | //$app->add(GZIP::class); 58 | }; 59 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "per-seo/cms", 3 | "type": "project", 4 | "description": "A simple modular CMS optimized for SEO based on Slim v4", 5 | "keywords": [ 6 | "Slim", 7 | "PerSeo", 8 | "PSR" 9 | ], 10 | "homepage": "https://github.com/per-seo/cms", 11 | "license": "MIT", 12 | "require": { 13 | "php": ">=7.4", 14 | "slim/slim": ">=4.0", 15 | "slim/psr7": ">=1.5", 16 | "slim/http": ">=1.3", 17 | "nyholm/psr7": ">=1.4", 18 | "nyholm/psr7-server": ">=1.0", 19 | "laminas/laminas-diactoros": ">=2.5", 20 | "php-di/php-di": ">=6.4", 21 | "brainstormdevel/basepath": ">=2.0", 22 | "slim/twig-view": ">=3.0", 23 | "catfan/medoo": ">=2.1", 24 | "composer/ca-bundle": ">=1.2", 25 | "odan/twig-assets": ">=3.2", 26 | "odan/session": ">=5.1", 27 | "monolog/monolog": ">=2.3", 28 | "ext-openssl": ">=7.2", 29 | "robmorgan/phinx": ">=0.13", 30 | "per-seo/locale": ">=1.0", 31 | "per-seo/gzip": ">=1.0", 32 | "per-seo/language": ">=1.0", 33 | "per-seo/forwarded-proto": ">=1.0", 34 | "per-seo/httphtmlexception": ">=1.0", 35 | "per-seo/jsonerror": "^1.0", 36 | "firebase/php-jwt": "^6.0" 37 | }, 38 | "require-dev": { 39 | "friendsofphp/php-cs-fixer": ">=3", 40 | "phpstan/phpstan": ">=1.0", 41 | "phpunit/phpunit": ">=10.5", 42 | "selective/test-traits": ">=4", 43 | "squizlabs/php_codesniffer": ">=3" 44 | }, 45 | "autoload": { 46 | "psr-4": { 47 | "PerSeo\\": "core/perseo", 48 | "Modules\\": "modules/" 49 | } 50 | }, 51 | "config": { 52 | "optimize-autoloader": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config/default.php: -------------------------------------------------------------------------------- 1 | 1, 12 | 'settings_global' => [ 13 | 'template' => 'default', 14 | 'locale' => false, 15 | 'language' => 'en', 16 | 'languages' => ['it', 'en'], 17 | 'adminpath' => 'admin' 18 | ], 19 | 'settings_root' => realpath(__DIR__ .'/..'), 20 | 'settings_temp' => realpath(__DIR__ .'/../tmp'), 21 | 'settings_modules' => realpath(__DIR__ .'/../modules'), 22 | 'settings_error' => [ 23 | 'reporting' => ['E_ALL', '~E_NOTICE'], 24 | 'display_error_details' => true, 25 | 'log_errors' => true, 26 | 'log_error_details' => true 27 | ], 28 | 'settings_session' =>[ 29 | 'name' => 'client', 30 | 'cache_expire' => 0, 31 | ], 32 | 'settings_twig' => [ 33 | // Template paths 34 | 'paths' => [ 35 | realpath(__DIR__ .'/../templates'), 36 | ], 37 | 'debug' => true, 38 | 'path' => realpath(__DIR__ .'/../cache'), 39 | 'url_base_path' => 'cache/', 40 | // Cache settings 41 | 'cache_enabled' => false, 42 | 'cache_path' => realpath(__DIR__ .'/../tmp'), 43 | 'cache_name' => 'assets-cache', 44 | // Should be set to 1 (enabled) in production 45 | 'minify' => 0, 46 | ], 47 | 'settings_logger' => [ 48 | 'name' => 'perseo', 49 | 'path' => realpath(__DIR__ .'/../logs'), 50 | 'filename' => 'perseo.log', 51 | 'level' => \Monolog\Logger::DEBUG, 52 | 'file_permission' => 0775, 53 | ] 54 | ]; 55 | -------------------------------------------------------------------------------- /modules/wizard/Views/Main.php: -------------------------------------------------------------------------------- 1 | app = $app; 23 | $this->container = $container; 24 | $this->twig = $twig; 25 | $this->settings = ($container->has('settings_global') ? $container->get('settings_global') : ['template' => 'default']); 26 | $this->template = $this->settings['template']; 27 | } 28 | 29 | public function __invoke(Request $request, Response $response): Response { 30 | $config = $this->container->get('settings_root') .'/config'; 31 | $module = $this->container->get('settings_modules') .'/wizard'; 32 | $lang = new Translator($request->getAttribute('language'), $module); 33 | $langs = $lang->get(); 34 | $viewData = [ 35 | 'basepath' => (string) $this->app->getBasePath(), 36 | 'cookiepath' => (string) (!empty($this->app->getBasePath()) ? $this->app->getBasePath() : '') .'/', 37 | 'writeperm' => (is_writable($config) ? "ok" : "no"), 38 | 'language' => $request->getAttribute('language'), 39 | 'openssl' => (extension_loaded('openssl') ? "ok" : "no"), 40 | 'lang' => $langs['body'], 41 | 'template' => $this->template 42 | ]; 43 | return $this->twig->render($response, $this->template . DIRECTORY_SEPARATOR .'wizard.twig', $viewData); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /modules/admin/Controllers/CLogout.php: -------------------------------------------------------------------------------- 1 | app = $app; 21 | $this->session = $session; 22 | $this->settingcookie = $container->get('settings_cookie'); 23 | } 24 | 25 | public function __invoke(Request $request, Response $response): Response 26 | { 27 | // Clear all admin session variables set by MLogin 28 | $this->session->delete('admin.login'); 29 | $this->session->delete('admin.id'); 30 | $this->session->delete('admin.ulid'); 31 | $this->session->delete('admin.user'); 32 | $this->session->delete('admin.permissions'); 33 | 34 | // Destroy the entire session 35 | $this->session->destroy(); 36 | 37 | // Clear JWT cookie using Slim PSR-7 Cookies 38 | $cookies = new Cookies(); 39 | $cookies->set($this->settingcookie['admin'], [ 40 | 'value' => '', 41 | 'expires' => time() - 3600, // Expire in the past 42 | 'path' => $this->settingcookie['cookie_path'] 43 | ]); 44 | 45 | // Apply cookie deletion to response 46 | foreach ($cookies->toHeaders() as $header) { 47 | $response = $response->withAddedHeader('Set-Cookie', $header); 48 | } 49 | 50 | // Redirect to login page 51 | $basepath = (string) $this->app->getBasePath(); 52 | return $response 53 | ->withHeader('Location', $basepath . '/admin/login') 54 | ->withStatus(302); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes 2 | DirectoryIndex index.php 3 | 4 | 5 | RewriteEngine On 6 | RewriteCond %{REQUEST_URI} !\.(gif|jpe?g|png|css|js|json|ico|svg|mp4)$ [NC,OR] 7 | RewriteCond %{REQUEST_FILENAME} !-f 8 | RewriteCond %{REQUEST_FILENAME} !-d 9 | RewriteRule ^([^/]+)/?(.*)$ index.php?language=$1&url=$2 [L,QSA] 10 | 11 | 12 | AddType application/x-javascript .js 13 | AddType text/css .css 14 | 15 | 16 | ExpiresActive On 17 | ExpiresDefault "access plus 10 days" 18 | ExpiresByType image/gif "access plus 1 year" 19 | ExpiresByType image/png "access plus 1 year" 20 | ExpiresByType image/jpg "access plus 1 year" 21 | ExpiresByType image/jpeg "access plus 1 year" 22 | ExpiresByType text/css "access plus 1 year" 23 | ExpiresByType text/plain "access plus 1 year" 24 | ExpiresByType application/x-javascript "access plus 1 month" 25 | ExpiresByType application/javascript "access plus 1 month" 26 | ExpiresByType application/x-icon "access plus 1 year" 27 | 28 | 29 | SetOutputFilter DEFLATE 30 | 31 | SetEnvIfNoCase Request_URI \.(?:rar|zip)$ no-gzip dont-vary 32 | SetEnvIfNoCase Request_URI \.(?:gif|jpg|png)$ no-gzip dont-vary 33 | SetEnvIfNoCase Request_URI \.(?:avi|mov|mp4)$ no-gzip dont-vary 34 | SetEnvIfNoCase Request_URI \.mp3$ no-gzip dont-vary 35 | 36 | 37 | BrowserMatch ^Mozilla/4 gzip-only-text/html 38 | BrowserMatch ^Mozilla/4\.0[678] no-gzip 39 | BrowserMatch \bMSIE !no-gzip !gzip-only-text/html 40 | 41 | 42 | Header append Vary User-Agent env=!dont-vary 43 | 44 | Header append Vary: Accept-Encoding 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /modules/wizard/Classes/TestMySQLDB.php: -------------------------------------------------------------------------------- 1 | (string) $params['driver'], 16 | 'host' => (string) $params['dbhost'], 17 | 'database' => (string) $params['dbname'], 18 | 'username' => (string) $params['dbuser'], 19 | 'password' => (string) $params['dbpass'], 20 | 'charset' => (string) $params['charset'], 21 | 'collation' => (string) $params['collation'], 22 | 'port' => (int) ((isset($params['dbport']) && !empty($params['dbport'])) ? $params['dbport'] : 3306) 23 | ]); 24 | $info = $db->info(); 25 | $version = (string) $info['version']; 26 | if(strpos($version, 'MariaDB') !== false){ 27 | $explode = explode("-", $version); 28 | $version = $explode[0]; 29 | if ($version < '10.0.5') { 30 | throw new Exception('Minimum requirements: Mariadb 10.0.5',0001); 31 | } 32 | } 33 | else { 34 | if ($version < '8.0.0') { 35 | throw new Exception('Minimum requirements: Mysql 8.0.0',0001); 36 | } 37 | } 38 | $result = array( 39 | "err" => 0, 40 | "code" => 0, 41 | "msg" => "ok" 42 | ); 43 | } catch (Exception $e) { 44 | $result = array( 45 | "err" => 1, 46 | "code" => $e->getCode(), 47 | "msg" => $e->getMessage() 48 | ); 49 | } 50 | return json_encode($result); 51 | } 52 | } -------------------------------------------------------------------------------- /templates/default/css/color/default.css: -------------------------------------------------------------------------------- 1 | a, a:hover{ 2 | color: #529abb; 3 | } 4 | .navbar-default{ 5 | background: #529abb; 6 | } 7 | 8 | .form-control:focus { 9 | border-color: #529abb; 10 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 2px #529abb; 11 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 2px #529abb; 12 | } 13 | 14 | .service .carousel-indicators .active { 15 | background: #529abb; 16 | } 17 | 18 | .btn-theme { 19 | background: #529abb; 20 | } 21 | 22 | .lb-album li > a span{ 23 | background: #529abb; 24 | background: -moz-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%, #529abb 100%); 25 | background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(255,255,255,0.56)), color-stop(100%,#529abb)); 26 | background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 27 | background: -o-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 28 | background: -ms-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 29 | background: radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 30 | } 31 | .lb-overlay{ 32 | background: #529abb; 33 | background: -moz-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%, #529abb 100%); 34 | background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(255,255,255,0.56)), color-stop(100%,#529abb)); 35 | background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 36 | background: -o-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 37 | background: -ms-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 38 | background: radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,#529abb 100%); 39 | } -------------------------------------------------------------------------------- /core/perseo/MiddleWare/Admin.php: -------------------------------------------------------------------------------- 1 | app = $app; 22 | $this->container = $container; 23 | $this->settings = $container->get('settings_global'); 24 | } 25 | 26 | public function process(Request $request, RequestHandler $handler): Response 27 | { 28 | if (!empty($this->settings['adminpath'])) { 29 | $adminpath = '/'. $this->settings['adminpath']; 30 | $adminpath2 = $adminpath .'/'; 31 | $fulluri = (string) $request->getUri()->getPath(); 32 | $basepath = (string) $this->app->getBasePath(); 33 | $uri = (string) substr($fulluri, strlen($basepath)); 34 | if (($uri == '/admin') || (substr($uri, 0, 7) == '/admin/')) { 35 | throw new \Slim\Exception\HttpNotFoundException($request); 36 | } 37 | if (($adminpath == $uri) || (substr($uri, 0, strlen($adminpath) + 1) == $adminpath2)) { 38 | $basepath = (string) $this->app->getBasePath(); 39 | $mydest = (string) $basepath . str_replace($adminpath, '/admin', $uri); 40 | $request = $request->withUri($request->getUri()->withPath($mydest)); 41 | } 42 | } 43 | $response = $handler->handle($request); 44 | return $response; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/perseo/MiddleWare/Wizard.php: -------------------------------------------------------------------------------- 1 | app = $app; 21 | $this->container = $container; 22 | } 23 | 24 | public function process(Request $request, RequestHandler $handler): Response 25 | { 26 | $fulluri = (string) $request->getUri()->getPath(); 27 | $basepath = (string) $this->app->getBasePath(); 28 | $uri = (string) substr($fulluri, strlen($basepath)); 29 | preg_match("/(^\/(wizard\/)|\/(wizard\b))/i", $uri, $matches); 30 | if ($this->container->has('settings_default')) { 31 | if (empty($matches[0])) { 32 | if ($request->getMethod() == 'GET') { 33 | $mydest = $basepath .'/wizard/'; 34 | $response = $this->app->getResponseFactory()->createResponse(); 35 | return $response->withHeader('Location', $mydest)->withStatus(301); 36 | } elseif ($request->getMethod() == 'POST') { 37 | throw new \Slim\Exception\HttpNotFoundException($request); 38 | } 39 | } 40 | } else { 41 | if (!empty($matches[0])) { 42 | throw new \Slim\Exception\HttpNotFoundException($request); 43 | } 44 | } 45 | $response = $handler->handle($request); 46 | return $response; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /templates/default/fonts/font-awesome-4.7.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /modules/wizard/Controllers/Test.php: -------------------------------------------------------------------------------- 1 | container = $container; 20 | } 21 | 22 | public function __invoke(Request $request, Response $response): Response { 23 | try { 24 | $post = $request->getParsedBody(); 25 | if ($post['driver'] == 'mysql') { 26 | $test = new TestMySQLDB(); 27 | $result = json_decode($test($post)); 28 | if ($result->err > 0) { throw new Exception($result->msg, (int) $result->code); } 29 | } 30 | elseif ($post['driver'] == 'pgsql') { 31 | $test = new TestPostgreDB(); 32 | $result = json_decode($test($post)); 33 | if ($result->err > 0) { throw new Exception($result->msg, (int) $result->code); } 34 | } 35 | elseif ($post['driver'] == 'sqlite') { 36 | $configpath = $this->container->get('settings_root') .'/config'; 37 | $test = new TestSQLiteDB($configpath); 38 | $result = json_decode($test($post)); 39 | if ($result->err > 0) { throw new Exception($result->msg, (int) $result->code); } 40 | } 41 | else { 42 | $result = array( 43 | "err" => 0, 44 | "code" => 0, 45 | "msg" => "ok" 46 | ); 47 | } 48 | } catch (Exception $e) { 49 | $result = Array( 50 | "err" => 1, 51 | "code" => $e->getCode(), 52 | "msg" => $e->getMessage() 53 | ); 54 | } 55 | $response->getBody()->write(json_encode($result)); 56 | return $response; 57 | } 58 | } -------------------------------------------------------------------------------- /modules/admin/Helpers/AuthHelper.php: -------------------------------------------------------------------------------- 1 | session = $session; 17 | $this->db = $db; 18 | } 19 | 20 | public function login($user): void 21 | { 22 | $this->session->set('user', $user); 23 | $this->session->set('authenticated', true); 24 | } 25 | 26 | public function logout(): void 27 | { 28 | $this->session->destroy(); 29 | } 30 | 31 | public function isAuthenticated(): bool 32 | { 33 | return $this->session->has('user') && $this->session->get('authenticated') === true; 34 | } 35 | 36 | public function getUser(): ?array 37 | { 38 | return $this->session->get('user'); 39 | } 40 | 41 | public function getUserId(): ?int 42 | { 43 | $user = $this->getUser(); 44 | return $user ? (int)$user['id'] : null; 45 | } 46 | 47 | public function hasPermission(string $permission): bool 48 | { 49 | if (!$this->isAuthenticated()) { 50 | return false; 51 | } 52 | 53 | $user = $this->getUser(); 54 | $userModel = new User($this->db); 55 | 56 | return $userModel->hasPermission($user['id'], $permission); 57 | } 58 | 59 | public function isAdmin(): bool 60 | { 61 | if (!$this->isAuthenticated()) { 62 | return false; 63 | } 64 | 65 | $user = $this->getUser(); 66 | return isset($user['role_id']) && $user['role_id'] == 1; 67 | } 68 | 69 | public function hasRole(string $roleSlug): bool 70 | { 71 | if (!$this->isAuthenticated()) { 72 | return false; 73 | } 74 | 75 | $user = $this->getUser(); 76 | return isset($user['role_slug']) && $user['role_slug'] === $roleSlug; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /templates/default/js/404.js: -------------------------------------------------------------------------------- 1 | gsap.set("svg", { visibility: "visible" }); 2 | gsap.to("#headStripe", { 3 | y: 0.5, 4 | rotation: 1, 5 | yoyo: true, 6 | repeat: -1, 7 | ease: "sine.inOut", 8 | duration: 1 9 | }); 10 | gsap.to("#spaceman", { 11 | y: 0.5, 12 | rotation: 1, 13 | yoyo: true, 14 | repeat: -1, 15 | ease: "sine.inOut", 16 | duration: 1 17 | }); 18 | gsap.to("#craterSmall", { 19 | x: -3, 20 | yoyo: true, 21 | repeat: -1, 22 | duration: 1, 23 | ease: "sine.inOut" 24 | }); 25 | gsap.to("#craterBig", { 26 | x: 3, 27 | yoyo: true, 28 | repeat: -1, 29 | duration: 1, 30 | ease: "sine.inOut" 31 | }); 32 | gsap.to("#planet", { 33 | rotation: -2, 34 | yoyo: true, 35 | repeat: -1, 36 | duration: 1, 37 | ease: "sine.inOut", 38 | transformOrigin: "50% 50%" 39 | }); 40 | 41 | gsap.to("#starsBig g", { 42 | rotation: "random(-30,30)", 43 | transformOrigin: "50% 50%", 44 | yoyo: true, 45 | repeat: -1, 46 | ease: "sine.inOut" 47 | }); 48 | gsap.fromTo( 49 | "#starsSmall g", 50 | { scale: 0, transformOrigin: "50% 50%" }, 51 | { scale: 1, transformOrigin: "50% 50%", yoyo: true, repeat: -1, stagger: 0.1 } 52 | ); 53 | gsap.to("#circlesSmall circle", { 54 | y: -4, 55 | yoyo: true, 56 | duration: 1, 57 | ease: "sine.inOut", 58 | repeat: -1 59 | }); 60 | gsap.to("#circlesBig circle", { 61 | y: -2, 62 | yoyo: true, 63 | duration: 1, 64 | ease: "sine.inOut", 65 | repeat: -1 66 | }); 67 | 68 | gsap.set("#glassShine", { x: -68 }); 69 | 70 | gsap.to("#glassShine", { 71 | x: 80, 72 | duration: 2, 73 | rotation: -30, 74 | ease: "expo.inOut", 75 | transformOrigin: "50% 50%", 76 | repeat: -1, 77 | repeatDelay: 8, 78 | delay: 2 79 | }); 80 | 81 | document.addEventListener('DOMContentLoaded', () => { 82 | const burger = document.querySelector('.burger'); 83 | const nav = document.querySelector('nav'); 84 | 85 | if (burger && nav) { 86 | burger.addEventListener('click', (e) => { 87 | burger.dataset.state === 'closed' ? burger.dataset.state = "open" : burger.dataset.state = "closed"; 88 | nav.dataset.state === "closed" ? nav.dataset.state = "open" : nav.dataset.state = "closed"; 89 | }); 90 | } 91 | }); -------------------------------------------------------------------------------- /core/perseo/MiddleWare/Maintenance.php: -------------------------------------------------------------------------------- 1 | app = $app; 20 | $this->container = $container; 21 | } 22 | 23 | public function process(Request $request, RequestHandler $handler): Response 24 | { 25 | $cookies = $request->getCookieParams(); 26 | if ($this->container->has('settings_global')) { 27 | $settings = $this->container->get('settings_global'); 28 | $fulluri = (string) $request->getUri()->getPath(); 29 | $basepath = (string) $this->app->getBasePath(); 30 | $uri = (string) substr($fulluri, strlen($basepath)); 31 | $language = $request->getAttribute('language'); 32 | $locale = (!empty($settings['locale']) ? '/'. $language : ''); 33 | if (!empty($settings['maintenance'])) { 34 | if (!isset($cookies['maintenance']) || ($cookies['maintenance'] != $settings['maintenancekey'])) { 35 | $mydest = (string) $basepath . $locale .'/maintenance'; 36 | if ($uri != $locale .'/maintenance') { 37 | $response = $this->app->getResponseFactory()->createResponse(); 38 | return $response->withHeader('Location', $mydest)->withStatus(301); 39 | } 40 | } 41 | } else { 42 | if ($uri == $locale .'/maintenance') { 43 | $mydest = (string) $basepath .'/'; 44 | $response = $this->app->getResponseFactory()->createResponse(); 45 | return $response->withHeader('Location', $mydest)->withStatus(301); 46 | } 47 | } 48 | } 49 | $response = $handler->handle($request); 50 | return $response; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modules/admin/Models/BaseModel.php: -------------------------------------------------------------------------------- 1 | db = $db; 15 | } 16 | 17 | public function find($id) 18 | { 19 | return $this->db->get($this->table, '*', ['id' => $id]); 20 | } 21 | 22 | public function findBy($column, $value) 23 | { 24 | return $this->db->get($this->table, '*', [$column => $value]); 25 | } 26 | 27 | public function all($columns = '*', $where = []) 28 | { 29 | return $this->db->select($this->table, $columns, $where); 30 | } 31 | 32 | public function paginate($page = 1, $perPage = 20, $columns = '*', $where = []) 33 | { 34 | $offset = ($page - 1) * $perPage; 35 | $data = $this->db->select($this->table, $columns, array_merge($where, [ 36 | 'LIMIT' => [$offset, $perPage] 37 | ])); 38 | 39 | $total = $this->db->count($this->table, $where); 40 | 41 | return [ 42 | 'data' => $data, 43 | 'total' => $total, 44 | 'page' => $page, 45 | 'perPage' => $perPage, 46 | 'totalPages' => ceil($total / $perPage) 47 | ]; 48 | } 49 | 50 | public function create($data) 51 | { 52 | $data['created_at'] = date('Y-m-d H:i:s'); 53 | $data['updated_at'] = date('Y-m-d H:i:s'); 54 | 55 | $this->db->insert($this->table, $data); 56 | return $this->db->id(); 57 | } 58 | 59 | public function update($id, $data) 60 | { 61 | $data['updated_at'] = date('Y-m-d H:i:s'); 62 | 63 | return $this->db->update($this->table, $data, ['id' => $id]); 64 | } 65 | 66 | public function delete($id) 67 | { 68 | return $this->db->delete($this->table, ['id' => $id]); 69 | } 70 | 71 | public function count($where = []) 72 | { 73 | return $this->db->count($this->table, $where); 74 | } 75 | 76 | public function exists($column, $value, $excludeId = null) 77 | { 78 | $where = [$column => $value]; 79 | if ($excludeId) { 80 | $where['id[!]'] = $excludeId; 81 | } 82 | return $this->db->has($this->table, $where); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /modules/admin/Models/Media.php: -------------------------------------------------------------------------------- 1 | db->select($this->table, [ 21 | '[>]admins' => ['uploaded_by' => 'id'] 22 | ], [ 23 | 'media.id', 24 | 'media.filename', 25 | 'media.original_name', 26 | 'media.path', 27 | 'media.mime_type', 28 | 'media.size', 29 | 'media.width', 30 | 'media.height', 31 | 'media.alt_text', 32 | 'media.caption', 33 | 'media.created_at', 34 | 'admins.user(uploader_name)' 35 | ], array_merge($where, [ 36 | 'ORDER' => ['media.created_at' => 'DESC'], 37 | 'LIMIT' => [$offset, $perPage] 38 | ])); 39 | 40 | $total = $this->db->count($this->table, $where); 41 | 42 | return [ 43 | 'data' => $data, 44 | 'total' => $total, 45 | 'page' => $page, 46 | 'perPage' => $perPage, 47 | 'totalPages' => ceil($total / $perPage) 48 | ]; 49 | } 50 | 51 | public function upload($fileData, $userId) 52 | { 53 | $data = [ 54 | 'filename' => $fileData['filename'], 55 | 'original_name' => $fileData['original_name'], 56 | 'path' => $fileData['path'], 57 | 'mime_type' => $fileData['mime_type'], 58 | 'size' => $fileData['size'], 59 | 'uploaded_by' => $userId 60 | ]; 61 | 62 | if (isset($fileData['width'])) { 63 | $data['width'] = $fileData['width']; 64 | } 65 | if (isset($fileData['height'])) { 66 | $data['height'] = $fileData['height']; 67 | } 68 | if (isset($fileData['alt_text'])) { 69 | $data['alt_text'] = $fileData['alt_text']; 70 | } 71 | if (isset($fileData['caption'])) { 72 | $data['caption'] = $fileData['caption']; 73 | } 74 | 75 | return $this->create($data); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config/bootstrap.php: -------------------------------------------------------------------------------- 1 | addDefinitions((file_exists(__DIR__ . '/settings.php') ? __DIR__ . '/settings.php' : __DIR__ . '/default.php')); 43 | $containerBuilder->addDefinitions(__DIR__ . '/container.php'); 44 | 45 | // Build PHP-DI Container instance 46 | $container = $containerBuilder->build(); 47 | 48 | $logger = $container->get(LoggerInterface::class); 49 | 50 | // Create App instance 51 | $app = $container->get(App::class); 52 | 53 | $serverRequestCreator = ServerRequestCreatorFactory::create(); 54 | $request = $serverRequestCreator->createServerRequestFromGlobals(); 55 | $decoratedRequest = new DecoratedServerRequest($request); 56 | $errorHandler = new HttpErrorHandler($app->getCallableResolver(), $app->getResponseFactory()); 57 | $shutdownHandler = new ShutdownHandler($request, $errorHandler, $logger, $container, true); 58 | register_shutdown_function($shutdownHandler); 59 | 60 | // Register routes 61 | (require __DIR__ . '/routes.php')($app); 62 | 63 | // Register middleware 64 | (require __DIR__ . '/middleware.php')($app); 65 | 66 | return $app; -------------------------------------------------------------------------------- /templates/default/css/flags.css: -------------------------------------------------------------------------------- 1 | .flag { 2 | width: 16px; 3 | height: 11px; 4 | background:url(flags.png) no-repeat 5 | } 6 | 7 | .flag.flag-ad {background-position: -16px 0} 8 | .flag.flag-al {background-position: -32px 0} 9 | .flag.flag-at {background-position: -48px 0} 10 | .flag.flag-ba {background-position: -64px 0} 11 | .flag.flag-be {background-position: -80px 0} 12 | .flag.flag-bg {background-position: -96px 0} 13 | .flag.flag-by {background-position: 0 -11px} 14 | .flag.flag-ch {background-position: -16px -11px} 15 | .flag.flag-cz {background-position: -32px -11px} 16 | .flag.flag-de {background-position: -48px -11px} 17 | .flag.flag-dk {background-position: -64px -11px} 18 | .flag.flag-ee {background-position: -80px -11px} 19 | .flag.flag-es {background-position: -96px -11px} 20 | .flag.flag-fi {background-position: 0 -22px} 21 | .flag.flag-fo {background-position: -16px -22px} 22 | .flag.flag-fr {background-position: -32px -22px} 23 | .flag.flag-en {background-position: -48px -22px} 24 | .flag.flag-gi {background-position: -64px -22px} 25 | .flag.flag-gr {background-position: -80px -22px} 26 | .flag.flag-hr {background-position: -96px -22px} 27 | .flag.flag-hu {background-position: 0 -33px} 28 | .flag.flag-ie {background-position: -16px -33px} 29 | .flag.flag-is {background-position: -32px -33px} 30 | .flag.flag-it {background-position: -48px -33px} 31 | .flag.flag-li {background-position: -64px -33px} 32 | .flag.flag-lt {background-position: -80px -33px} 33 | .flag.flag-lu {background-position: -96px -33px} 34 | .flag.flag-lv {background-position: 0 -44px} 35 | .flag.flag-mc {background-position: -16px -44px} 36 | .flag.flag-md {background-position: -32px -44px} 37 | .flag.flag-me {background-position: -48px -44px} 38 | .flag.flag-mk {background-position: -64px -44px} 39 | .flag.flag-mt {background-position: -80px -44px} 40 | .flag.flag-nl {background-position: -96px -44px} 41 | .flag.flag-no {background-position: 0 -55px} 42 | .flag.flag-pl {background-position: -16px -55px} 43 | .flag.flag-pt {background-position: -32px -55px} 44 | .flag.flag-ro {background-position: -48px -55px} 45 | .flag.flag-rs {background-position: -64px -55px} 46 | .flag.flag-ru {background-position: -80px -55px} 47 | .flag.flag-se {background-position: -96px -55px} 48 | .flag.flag-si {background-position: 0 -66px} 49 | .flag.flag-sk {background-position: -16px -66px} 50 | .flag.flag-sm {background-position: -32px -66px} 51 | .flag.flag-ua {background-position: -48px -66px} 52 | .flag.flag-va {background-position: -64px -66px} 53 | .flag.flag-xk {background-position: -80px -66px} -------------------------------------------------------------------------------- /modules/admin/Models/Role.php: -------------------------------------------------------------------------------- 1 | db->get($this->table, '*', ['slug' => $slug]); 19 | } 20 | 21 | public function getRoleWithPermissions($roleId) 22 | { 23 | $role = $this->find($roleId); 24 | if (!$role) { 25 | return null; 26 | } 27 | 28 | $permissions = $this->db->select('permissions', [ 29 | '[>]role_permissions' => ['id' => 'permission_id'] 30 | ], [ 31 | 'permissions.id', 32 | 'permissions.slug', 33 | 'permissions.description' 34 | ], [ 35 | 'role_permissions.role_id' => $roleId 36 | ]); 37 | 38 | $role['permissions'] = $permissions; 39 | return $role; 40 | } 41 | 42 | public function getAllWithPermissions() 43 | { 44 | $roles = $this->all(); 45 | foreach ($roles as &$role) { 46 | $permissions = $this->db->select('permissions', [ 47 | '[>]role_permissions' => ['id' => 'permission_id'] 48 | ], [ 49 | 'permissions.id', 50 | 'permissions.slug' 51 | ], [ 52 | 'role_permissions.role_id' => $role['id'] 53 | ]); 54 | $role['permissions'] = $permissions; 55 | } 56 | return $roles; 57 | } 58 | 59 | public function assignPermission($roleId, $permissionId) 60 | { 61 | return $this->db->insert('role_permissions', [ 62 | 'role_id' => $roleId, 63 | 'permission_id' => $permissionId 64 | ]); 65 | } 66 | 67 | public function removePermission($roleId, $permissionId) 68 | { 69 | return $this->db->delete('role_permissions', [ 70 | 'role_id' => $roleId, 71 | 'permission_id' => $permissionId 72 | ]); 73 | } 74 | 75 | public function syncPermissions($roleId, $permissionIds) 76 | { 77 | // Remove all existing permissions 78 | $this->db->delete('role_permissions', ['role_id' => $roleId]); 79 | 80 | // Add new permissions 81 | foreach ($permissionIds as $permissionId) { 82 | $this->assignPermission($roleId, $permissionId); 83 | } 84 | 85 | return true; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /modules/admin/Models/MLogin.php: -------------------------------------------------------------------------------- 1 | session = $session; 16 | $this->db = $database; 17 | } 18 | 19 | public function verify(string $user_email, string $pass) 20 | { 21 | try { 22 | if (empty($user_email) || empty($pass)) { 23 | throw new \Exception('MISSING_PARAMETERS', 001); 24 | } 25 | $login_info = $this->db->query("SELECT as id, as ulid, as user, as pass, as email, CONCAT('[',perm.perms,']') as permissions FROM 26 | INNER JOIN (SELECT as role_id, GROUP_CONCAT(JSON_OBJECT('id', , 'slug', ) ORDER BY ASC) AS perms FROM 27 | INNER JOIN ON = 28 | INNER JOIN ON = GROUP BY ) perm ON = perm.role_id WHERE = 1 AND ( = :user OR = :email)", [ 29 | ":user" => $user_email, 30 | ":email" => $user_email 31 | ])->fetchAll(\PDO::FETCH_ASSOC); 32 | if (empty($login_info)) { 33 | throw new \Exception("USR_PASS_ERR", 004); 34 | } 35 | $error = isset($this->db->error) ? $this->db->error : null; 36 | if ($error != null) { 37 | if (($error[1] != null) && ($error[2] != null)) { 38 | throw new \Exception($error[2], 1); 39 | } 40 | } 41 | if (password_verify($pass, $login_info[0]['pass'])) { 42 | $this->session->set('admin.login', true); 43 | $this->session->set('admin.id', (int) $login_info[0]['id']); 44 | $this->session->set('admin.ulid', (string) $login_info[0]['ulid']); 45 | $this->session->set('admin.user', (string) $login_info[0]['user']); 46 | $this->session->set('admin.permissions', (string) $login_info[0]['permissions']); 47 | } else { 48 | throw new \Exception("USR_PASS_ERR", 004); 49 | } 50 | 51 | $result = array( 52 | 'success' => 1, 53 | 'error' => 0, 54 | 'code' => '0', 55 | 'msg' => 'OK' 56 | ); 57 | } catch (\Exception $e) { 58 | $result = array( 59 | 'success' => 0, 60 | 'error' => 1, 61 | 'code' => $e->getCode(), 62 | 'msg' => $e->getMessage() 63 | ); 64 | } 65 | return json_encode($result); 66 | } 67 | } -------------------------------------------------------------------------------- /core/perseo/MiddleWare/ErrorHandlerMiddleware.php: -------------------------------------------------------------------------------- 1 | errorHandler = $errorHandler; 35 | $this->logger = $logger; 36 | } 37 | 38 | /** 39 | * Invoke middleware. 40 | * 41 | * @param ServerRequestInterface $request The request 42 | * @param RequestHandlerInterface $handler The handler 43 | * 44 | * @return ResponseInterface The response 45 | */ 46 | public function process( 47 | ServerRequestInterface $request, 48 | RequestHandlerInterface $handler 49 | ): ResponseInterface { 50 | $errorTypes = E_ALL; 51 | 52 | set_error_handler( 53 | function ($errno, $errstr, $errfile, $errline) { 54 | switch ($errno) { 55 | case E_USER_ERROR: 56 | $this->logger->error( 57 | "Error number [$errno] $errstr on line $errline in file $errfile" 58 | ); 59 | break; 60 | case E_USER_WARNING: 61 | $this->logger->warning( 62 | "Error number [$errno] $errstr on line $errline in file $errfile" 63 | ); 64 | break; 65 | default: 66 | $this->logger->notice( 67 | "Error number [$errno] $errstr on line $errline in file $errfile" 68 | ); 69 | break; 70 | } 71 | // Don't execute PHP internal error handler 72 | return true; 73 | }, 74 | $errorTypes 75 | ); 76 | set_exception_handler( 77 | function (Throwable $exception) { 78 | $this->logger->error("errore"); 79 | //$this->logger->error($exception); 80 | } 81 | ); 82 | 83 | return $handler->handle($request); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /templates/default/js/editorjs/code.umd.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".ce-code__textarea{min-height:200px;font-family:Menlo,Monaco,Consolas,Courier New,monospace;color:#41314e;line-height:1.6em;font-size:12px;background:#f8f7fa;border:1px solid #f1f1f4;box-shadow:none;white-space:pre;word-wrap:normal;overflow-x:auto;resize:vertical}")),document.head.appendChild(e)}}catch(o){console.error("vite-plugin-css-injected-by-js",o)}})(); 2 | (function(i,o){typeof exports=="object"&&typeof module<"u"?module.exports=o():typeof define=="function"&&define.amd?define(o):(i=typeof globalThis<"u"?globalThis:i||self,i.CodeTool=o())})(this,function(){"use strict";function i(h,t){let s="";for(;s!==` 3 | `&&t>0;)t=t-1,s=h.substr(t,1);return s===` 4 | `&&(t+=1),t}const o='';/** 5 | * CodeTool for Editor.js 6 | * @version 2.0.0 7 | * @license MIT 8 | */class l{static get isReadOnlySupported(){return!0}static get enableLineBreaks(){return!0}constructor({data:t,config:e,api:s,readOnly:r}){this.api=s,this.readOnly=r,this.placeholder=this.api.i18n.t(e.placeholder||l.DEFAULT_PLACEHOLDER),this.CSS={baseClass:this.api.styles.block,input:this.api.styles.input,wrapper:"ce-code",textarea:"ce-code__textarea"},this.nodes={holder:null,textarea:null},this.data={code:t.code??""},this.nodes.holder=this.drawView()}render(){return this.nodes.holder}save(t){return{code:t.querySelector("textarea").value}}onPaste(t){const e=t.detail;if("data"in e){const s=e.data;this.data={code:s||""}}}get data(){return this._data}set data(t){this._data=t,this.nodes.textarea&&(this.nodes.textarea.value=t.code)}static get toolbox(){return{icon:o,title:"Code"}}static get DEFAULT_PLACEHOLDER(){return"Enter a code"}static get pasteConfig(){return{tags:["pre"]}}static get sanitize(){return{code:!0}}tabHandler(t){t.stopPropagation(),t.preventDefault();const e=t.target,s=t.shiftKey,r=e.selectionStart,a=e.value,n=" ";let d;if(!s)d=r+n.length,e.value=a.substring(0,r)+n+a.substring(r);else{const c=i(a,r);if(a.substr(c,n.length)!==n)return;e.value=a.substring(0,c)+a.substring(c+n.length),d=r-n.length}e.setSelectionRange(d,d)}drawView(){const t=document.createElement("div"),e=document.createElement("textarea");return t.classList.add(this.CSS.baseClass,this.CSS.wrapper),e.classList.add(this.CSS.textarea,this.CSS.input),e.value=this.data.code,e.placeholder=this.placeholder,this.readOnly&&(e.disabled=!0),t.appendChild(e),e.addEventListener("keydown",s=>{switch(s.code){case"Tab":this.tabHandler(s);break}}),this.nodes.textarea=e,t}}return l}); 9 | -------------------------------------------------------------------------------- /core/perseo/LoggerFactory.php: -------------------------------------------------------------------------------- 1 | path = (string)$settings['path']; 34 | $this->level = (int)$settings['level']; 35 | } 36 | 37 | /** 38 | * @var array Handler 39 | */ 40 | private $handler = []; 41 | 42 | /** 43 | * Build the logger. 44 | * 45 | * @param string $name The name 46 | * 47 | * @return LoggerInterface The logger 48 | */ 49 | public function createInstance(string $name): LoggerInterface 50 | { 51 | $logger = new Logger($name); 52 | 53 | foreach ($this->handler as $handler) { 54 | $logger->pushHandler($handler); 55 | } 56 | 57 | $this->handler = []; 58 | 59 | return $logger; 60 | } 61 | 62 | /** 63 | * Add rotating file logger handler. 64 | * 65 | * @param string $filename The filename 66 | * @param int $level The level (optional) 67 | * 68 | * @return LoggerFactory The logger factory 69 | */ 70 | public function addFileHandler(string $filename, int $level = null): self 71 | { 72 | $filename = sprintf('%s/%s', $this->path, $filename); 73 | 74 | $rotatingFileHandler = new RotatingFileHandler( 75 | $filename, 76 | 0, 77 | $level ?? $this->level, 78 | true, 79 | 0777 80 | ); 81 | 82 | // The last "true" here tells monolog to remove empty []'s 83 | $rotatingFileHandler->setFormatter( 84 | new LineFormatter(null, null, false, true) 85 | ); 86 | 87 | $this->handler[] = $rotatingFileHandler; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * Add a console logger. 94 | * 95 | * @param int $level The level (optional) 96 | * 97 | * @return self The instance 98 | */ 99 | public function addConsoleHandler(int $level = null): self 100 | { 101 | $streamHandler = new StreamHandler('php://stdout', $level ?? $this->level); 102 | $streamHandler->setFormatter(new LineFormatter(null, null, false, true)); 103 | 104 | $this->handler[] = $streamHandler; 105 | 106 | return $this; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /modules/wizard/Classes/TestPostgreDB.php: -------------------------------------------------------------------------------- 1 | (string) $params['driver'], 16 | 'host' => (string) $params['dbhost'], 17 | 'database' => (string) $params['dbname'], 18 | 'username' => (string) $params['dbuser'], 19 | 'password' => (string) $params['dbpass'], 20 | 'charset' => (string) $params['charset'], 21 | 'port' => (int) ((isset($params['dbport']) && !empty($params['dbport'])) ? $params['dbport'] : 5432) 22 | ]); 23 | $info = $db->info(); 24 | $version = (string) $info['version']; 25 | if ($version < '16.9') { 26 | throw new Exception('Minimum requirements: PostgreSQL 16.9',0001); 27 | } 28 | $result = array( 29 | "err" => 0, 30 | "code" => 0, 31 | "msg" => "ok" 32 | ); 33 | } catch (Exception $e) { 34 | $result = array( 35 | "err" => 1, 36 | "code" => $e->getCode(), 37 | "msg" => $e->getMessage() 38 | ); 39 | } 40 | return json_encode($result); 41 | } 42 | 43 | protected function createdb(string $dbfile): string { 44 | $result = array( 45 | "err" => 0, 46 | "code" => 0, 47 | "msg" => "ok" 48 | ); 49 | if (!file_exists($dbfile)) { 50 | try { 51 | touch($dbfile); 52 | chmod($dbfile, 0644); 53 | } catch (Exception $e) { 54 | $result = array( 55 | "err" => 1, 56 | "code" => $e->getCode(), 57 | "msg" => $e->getMessage() 58 | ); 59 | } 60 | } 61 | return json_encode($result); 62 | } 63 | protected function sanitizeFile(string $file): string { 64 | 65 | $decoded = rawurldecode($file); 66 | 67 | $normalized = str_replace('\\', '/', $decoded); 68 | 69 | $patterns = [ 70 | // Standard directory traversal 71 | '/\.\.\//', 72 | '/\.\.\\\\/', 73 | 74 | // Unicode / exotic variations 75 | '/\x2e\x2e[\x2f\x5c]/i', // ../ or ..\ 76 | '/\x{ff0e}\x{ff0e}[\x{ff0f}\x{ff3c}]/u', // Fullwidth .. + slash or backslash 77 | '/\x{2215}/u', // Unicode division slash 78 | '/\xef\xbc\x8f/', // Fullwidth solidus 79 | '/\xc0\xaf/', // Overlong UTF-8 / 80 | ]; 81 | $cleaned = preg_replace($patterns, '', $normalized); 82 | 83 | // 4. Remove null bytes and control characters 84 | $cleaned = preg_replace('/[\x00-\x1f\x7f]/', '', $cleaned); 85 | 86 | return trim($cleaned); 87 | } 88 | } -------------------------------------------------------------------------------- /modules/admin/Middleware/PermissionMiddleware.php: -------------------------------------------------------------------------------- 1 | app = $app; 23 | $this->container = $container; 24 | $this->session = $container->get(\Odan\Session\SessionInterface::class); 25 | $this->permission = $permission; 26 | } 27 | 28 | public function process(Request $request, RequestHandler $handler): Response 29 | { 30 | // Check if user is authenticated using MLogin session vars 31 | if (!$this->session->has('admin.login') || $this->session->get('admin.login') !== true) { 32 | $response = new SlimResponse(); 33 | $response->getBody()->write(json_encode([ 34 | 'success' => false, 35 | 'message' => 'Unauthorized' 36 | ])); 37 | return $response->withHeader('Content-Type', 'application/json')->withStatus(401); 38 | } 39 | 40 | // Get permissions from MLogin session 41 | // MLogin sets admin.permissions as JSON string: '[{"id":1,"slug":"manage_users"},{"id":2,"slug":"edit_posts"}]' 42 | $permissionsJson = $this->session->get('admin.permissions'); 43 | 44 | if (!$permissionsJson) { 45 | $response = new SlimResponse(); 46 | $response->getBody()->write(json_encode([ 47 | 'success' => false, 48 | 'message' => 'Forbidden: No permissions assigned' 49 | ])); 50 | return $response->withHeader('Content-Type', 'application/json')->withStatus(403); 51 | } 52 | 53 | // Parse permissions JSON 54 | $permissions = json_decode($permissionsJson, true) ?? []; 55 | 56 | // Check if user has the required permission 57 | $hasPermission = false; 58 | foreach ($permissions as $perm) { 59 | if (isset($perm['slug']) && $perm['slug'] === $this->permission) { 60 | $hasPermission = true; 61 | break; 62 | } 63 | } 64 | 65 | if (!$hasPermission) { 66 | $response = new SlimResponse(); 67 | $response->getBody()->write(json_encode([ 68 | 'success' => false, 69 | 'message' => 'Forbidden: You do not have permission to access this resource' 70 | ])); 71 | return $response->withHeader('Content-Type', 'application/json')->withStatus(403); 72 | } 73 | 74 | return $handler->handle($request); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /templates/default/js/editorjs/delimiter.mjs: -------------------------------------------------------------------------------- 1 | (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode('.ce-delimiter{line-height:1.6em;width:100%;text-align:center}.ce-delimiter:before{display:inline-block;content:"***";font-size:30px;line-height:65px;height:30px;letter-spacing:.2em}')),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})(); 2 | const r = ''; 3 | /** 4 | * Delimiter Block for the Editor.js. 5 | * 6 | * @author CodeX (team@ifmo.su) 7 | * @copyright CodeX 2018 8 | * @license The MIT License (MIT) 9 | * @version 2.0.0 10 | */ 11 | class n { 12 | /** 13 | * Notify core that read-only mode is supported 14 | * @return {boolean} 15 | */ 16 | static get isReadOnlySupported() { 17 | return !0; 18 | } 19 | /** 20 | * Allow Tool to have no content 21 | * @return {boolean} 22 | */ 23 | static get contentless() { 24 | return !0; 25 | } 26 | /** 27 | * Render plugin`s main Element and fill it with saved data 28 | * 29 | * @param {{data: DelimiterData, config: object, api: object}} 30 | * data — previously saved data 31 | * config - user config for Tool 32 | * api - Editor.js API 33 | */ 34 | constructor({ data: t, config: s, api: e }) { 35 | this.api = e, this._CSS = { 36 | block: this.api.styles.block, 37 | wrapper: "ce-delimiter" 38 | }, this._element = this.drawView(), this.data = t; 39 | } 40 | /** 41 | * Create Tool's view 42 | * @return {HTMLDivElement} 43 | * @private 44 | */ 45 | drawView() { 46 | let t = document.createElement("div"); 47 | return t.classList.add(this._CSS.wrapper, this._CSS.block), t; 48 | } 49 | /** 50 | * Return Tool's view 51 | * @returns {HTMLDivElement} 52 | * @public 53 | */ 54 | render() { 55 | return this._element; 56 | } 57 | /** 58 | * Extract Tool's data from the view 59 | * @param {HTMLDivElement} toolsContent - Paragraph tools rendered view 60 | * @returns {DelimiterData} - saved data 61 | * @public 62 | */ 63 | save(t) { 64 | return {}; 65 | } 66 | /** 67 | * Get Tool toolbox settings 68 | * icon - Tool icon's SVG 69 | * title - title to show in toolbox 70 | * 71 | * @return {{icon: string, title: string}} 72 | */ 73 | static get toolbox() { 74 | return { 75 | icon: r, 76 | title: "Delimiter" 77 | }; 78 | } 79 | /** 80 | * Delimiter onPaste configuration 81 | * 82 | * @public 83 | */ 84 | static get pasteConfig() { 85 | return { tags: ["HR"] }; 86 | } 87 | /** 88 | * On paste callback that is fired from Editor 89 | * 90 | * @param {PasteEvent} event - event with pasted data 91 | */ 92 | onPaste(t) { 93 | this.data = {}; 94 | } 95 | } 96 | export { 97 | n as default 98 | }; 99 | -------------------------------------------------------------------------------- /modules/wizard/Classes/Config.php: -------------------------------------------------------------------------------- 1 | fileconf = $fileconf; 12 | } 13 | 14 | public function base(array $params): string { 15 | try { 16 | $myfile = fopen($this->fileconf, "w"); 17 | $content = " [ 20 | 'sitename' => '" . (string) $params['title'] . "', 21 | 'encoding' => '" . (string) $params['encoding'] . "', 22 | 'template' => '" . (string) $params['template'] . "', 23 | 'locale' => " . (boolval($params['locale']) ? 'true' : 'false') . ", 24 | 'adminpath' => '" . (string) $params['adminpath'] . "', 25 | 'maintenance' => false, 26 | 'maintenancekey' => '" . (string) $params['maintenancekey'] . "', 27 | 'language' => '" . (string) $params['defaultlang'] . "', 28 | 'languages' => ['it', 'en'] 29 | ], 30 | 'settings_root' => realpath(__DIR__ .'/..'), 31 | 'settings_temp' => realpath(__DIR__ .'/../tmp'), 32 | 'settings_modules' => realpath(__DIR__ .'/../modules'), 33 | 'settings_error' => [ 34 | 'reporting' => ['E_ALL', '~E_NOTICE'], 35 | 'display_error_details' => false, 36 | 'log_errors' => true, 37 | 'log_error_details' => true 38 | ], 39 | 'settings_session' =>[ 40 | 'name' => '". substr(str_shuffle(str_repeat($x='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil(10/strlen($x)) )),1,10) ."', 41 | 'cache_expire' => 0, 42 | ], 43 | 'settings_twig' => [ 44 | // Template paths 45 | 'paths' => [ 46 | realpath(__DIR__ .'/../templates'), 47 | ], 48 | 'debug' => true, 49 | 'path' => realpath(__DIR__ .'/../cache'), 50 | 'url_base_path' => 'cache/', 51 | // Cache settings 52 | 'cache_enabled' => false, 53 | 'cache_path' => realpath(__DIR__ .'/../tmp'), 54 | 'cache_name' => 'assets-cache', 55 | // Should be set to 1 (enabled) in production 56 | 'minify' => 0, 57 | ], 58 | 'settings_logger' => [ 59 | 'name' => 'app', 60 | 'path' => realpath(__DIR__ .'/../logs'), 61 | 'filename' => 'app.log', 62 | 'level' => \Monolog\Logger::DEBUG, 63 | 'file_permission' => 0775, 64 | ], 65 | 'settings_secure' => [ 66 | 'crypt_salt' => '" . (string) $params['salt'] . "' 67 | ], 68 | 'settings_cookie' => [ 69 | 'admin' => '" . (string) $params['cookadm'] . "', 70 | 'user' => '" . (string) $params['cookusr'] . "', 71 | 'cookie_exp' => '" . (string) $params['cookexp'] . "', 72 | 'cookie_max_exp' => '" . (string) $params['cookmaxexp'] . "', 73 | 'cookie_path' => '" . (string) $params['cookpath'] . "', 74 | 'cookie_secure' => false, // Should be set to true in production 75 | 'cookie_http' => true 76 | ] 77 | ];"; 78 | fwrite($myfile, $content); 79 | fclose($myfile); 80 | $result = array( 81 | 'code' => '0', 82 | 'msg' => 'OK' 83 | ); 84 | } catch (Exception $e) { 85 | $result = array( 86 | 'code' => $e->getCode(), 87 | 'msg' => $e->getMessage() 88 | ); 89 | } 90 | return json_encode($result); 91 | } 92 | } -------------------------------------------------------------------------------- /modules/admin/Helpers/JWTHelper.php: -------------------------------------------------------------------------------- 1 | secretKey = $settings['jwt_secret'] ?? bin2hex(random_bytes(32)); 18 | 19 | if (isset($settings['jwt_expiration'])) { 20 | $this->expirationTime = (int) $settings['jwt_expiration']; 21 | } 22 | if (isset($settings['algorithm'])) { 23 | $this->algorithm = (string) $settings['algorithm']; 24 | } 25 | } 26 | 27 | /** 28 | * Generate JWT token with ULID and permissions 29 | */ 30 | public function generateToken(array $userData): string 31 | { 32 | $now = time(); 33 | 34 | $payload = [ 35 | 'iat' => $now, 36 | 'exp' => $now + $this->expirationTime, 37 | 'ulid' => $userData['ulid'] ?? '', 38 | 'permissions' => $userData['permissions'] ?? [] 39 | ]; 40 | 41 | return JWT::encode($payload, $this->secretKey, $this->algorithm); 42 | } 43 | 44 | /** 45 | * Validate and decode JWT token 46 | */ 47 | public function validateToken(string $token): ?\stdClass 48 | { 49 | try { 50 | $decoded = JWT::decode($token, new Key($this->secretKey, $this->algorithm)); 51 | 52 | // Check expiration 53 | if ($decoded->exp < time()) { 54 | return null; 55 | } 56 | 57 | return $decoded; 58 | } catch (\Exception $e) { 59 | return null; 60 | } 61 | } 62 | 63 | /** 64 | * Get user data from token 65 | */ 66 | public function getUserFromToken(string $token): ?array 67 | { 68 | $decoded = $this->validateToken($token); 69 | 70 | if (!$decoded) { 71 | return null; 72 | } 73 | 74 | return [ 75 | 'ulid' => $decoded->ulid ?? '', 76 | 'id' => $decoded->user_id ?? 0, 77 | 'permissions' => $decoded->permissions ?? [] 78 | ]; 79 | } 80 | 81 | /** 82 | * Extract token from request 83 | */ 84 | public function extractTokenFromRequest(\Psr\Http\Message\ServerRequestInterface $request, string $cookieName = 'perseo_jwt'): ?string 85 | { 86 | $cookies = $request->getCookieParams(); 87 | return $cookies[$cookieName] ?? null; 88 | } 89 | 90 | /** 91 | * Check if token is expired 92 | */ 93 | public function isTokenExpired(string $token): bool 94 | { 95 | try { 96 | $decoded = JWT::decode($token, new Key($this->secretKey, $this->algorithm)); 97 | return $decoded->exp < time(); 98 | } catch (\Exception $e) { 99 | return true; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /modules/wizard/languages/en.lng: -------------------------------------------------------------------------------- 1 | {"body":{"title":"Welcome to","l_welcome":"Welcome to","l_wizard_1":"The wizard will help you configure the CMS","l_wizard_2_1":"DB Access Parameters","l_wizard_3_1":"DB Parameters Test","l_wizard_4_1":"CMS Configurations (Keep default if you don't know)","l_wizard_5_1":"Configure Admin Info","l_wizard_6_1":"Install the CMS","l_check_perm":"Check write folder permissions:","l_check_perm_ok":"Folder Permissions are Correct","l_check_perm_no":"Permissions incorrect. Check Folder Permissions","l_check_openssl":"Check Openssl Extension Enabled:","l_check_openssl_ok":"Openssl Extension Enabled","l_check_openssl_no":"Openssl not present, unable to continue","l_check_db_ok":"Connections Succesfully","l_check_db_no":"Connection Failed:","l_dbtype":"DB Type:","l_dbtype_ph":"Select DB type","l_dbfile":"DB File:","l_dbfile_ph":"DB Name SQLite:","l_dbhost":"Host:","l_dbhost_ph":"DB Host","l_dbport":"Port:","l_dbport_ph":"DB Port (default leave empty)","l_dbname":"DB Name:","l_dbname_ph":"Insert DB Name","l_dbuser":"DB User:","l_dbuser_ph":"Insert DB User","l_dbpass":"DB Password:","l_dbpass_ph":"Insert DB Password","l_dbencoding":"DB Encoding:","l_dbencoding_ph":"Insert DB Encoding","l_dbcollation":"DB Collation:","l_dbcollation_ph":"Insert DB Collation","l_title":"Website Name","l_title_ph":"Insert Website Name","l_prefix_tb":"Table Prefix","l_prefix_tb_ph":"Insert Table Prefix","l_alias_admin":"Alias Admin","l_alias_admin_ph":"Add admin panel alias","l_prefix_salt":"Salt Password String","l_prefix_salt_ph":"Generate Salt Password String to Crypt Passwords","l_maintenance":"Maintenance Password","l_maintenance_ph":"Generate Maintenance Password","l_template_default":"Default Template","l_locale_url":"Locale in URL","l_yes":"YES","l_no":"NO","l_encoding_page":"Page Encoding","l_encoding_page_ph":"HTML Page Encoding","l_adm_cookname":"Cookie Admin Prefix","l_adm_cookname_ph":"Insert Cookie Admin Prefix","l_usr_cookname":"Cookie User Prefix","l_usr_cookname_ph":"Insert Cookie User Prefix","l_cookie_expire":"Cookie Lifetime (Sec.)","l_cookie_expire_ph":"Insert Cookie Lifetime","l_cookie_max_expire":"Max Cookie Lifetime (Sec.)","l_cookie_max_expire_ph":"Insert Max Cookie Lifetime","l_cookie_path":"Cookie Path","l_cookie_path_ph":"Insert Cookie Path","l_app_name":"App Name","l_app_name_ph":"Insert App Name","l_prefix_fname":"Prefix Log Name","l_prefix_fname_ph":"Insert Log Name Prefix","l_facebook_app":"Facebook APP","l_facebook_app_ph":"Add Facebook APP Key","l_facebook_secret":"Facebook Secret","l_facebook_secret_ph":"Add Facebook Secret Key","l_google_key":"Google API Key","l_google_key_ph":"Add Google API Key","l_google_secret":"Google Secret Key","l_google_secret_ph":"Add Google Secret Key","l_username":"Admin Username","l_username_ph":"Insert Admin Username","l_email":"Admin Email","l_email_ph":"Insert Admin Email","l_password":"Admin Password","l_password_ph":"Insert Admin Password","l_language":"Language","l_lang_default":"Default Language","l_generate":"Generate","l_install_ok":"Install Complete","l_install_ko":"Install Complete","l_start":"Start","l_second":"Database","l_third":"Test Parameters","l_forth":"Configure","l_fifth":"Login Info","l_sixth":"Install","l_previous":"Previous","l_next":"Next","l_install":"Install"}} -------------------------------------------------------------------------------- /modules/wizard/Classes/TestSQLiteDB.php: -------------------------------------------------------------------------------- 1 | configpath = $configpath; 15 | } 16 | 17 | public function __invoke(array $params): string { 18 | try { 19 | if (!extension_loaded('pdo_sqlite')) { throw new Exception('SQLite extension not present',0002); } 20 | $paramfile = $this->sanitizeFile($params['dbfile']); 21 | $prefix = $params['prefix']; 22 | $dbfile = $this->configpath . DIRECTORY_SEPARATOR . $paramfile; 23 | if (!file_exists($dbfile)) { 24 | $result = json_decode($this->createdb($dbfile)); 25 | if ($result->err > 0) { throw new Exception('Unable to open '. $dbfile .' db file',0001); } 26 | } 27 | $db = new DB([ 28 | 'type' => (string) $params['driver'], 29 | 'database' => (string) $dbfile 30 | ]); 31 | $db->create($prefix ."test", [ 32 | "id" => [ 33 | "INTEGER", 34 | "PRIMARY KEY" 35 | ] 36 | ]); 37 | $db->drop("test"); 38 | unset($db); 39 | unlink($dbfile); 40 | $result = array( 41 | "err" => 0, 42 | "code" => 0, 43 | "msg" => "ok" 44 | ); 45 | } catch (Exception $e) { 46 | $result = array( 47 | "err" => 1, 48 | "code" => $e->getCode(), 49 | "msg" => $e->getMessage() 50 | ); 51 | } 52 | return json_encode($result); 53 | } 54 | 55 | protected function createdb(string $dbfile): string { 56 | $result = array( 57 | "err" => 0, 58 | "code" => 0, 59 | "msg" => "ok" 60 | ); 61 | if (!file_exists($dbfile)) { 62 | try { 63 | touch($dbfile); 64 | chmod($dbfile, 0644); 65 | } catch (Exception $e) { 66 | $result = array( 67 | "err" => 1, 68 | "code" => $e->getCode(), 69 | "msg" => $e->getMessage() 70 | ); 71 | } 72 | } 73 | return json_encode($result); 74 | } 75 | protected function sanitizeFile(string $file): string { 76 | 77 | $decoded = rawurldecode($file); 78 | 79 | $normalized = str_replace('\\', '/', $decoded); 80 | 81 | $patterns = [ 82 | // Standard directory traversal 83 | '/\.\.\//', 84 | '/\.\.\\\\/', 85 | 86 | // Unicode / exotic variations 87 | '/\x2e\x2e[\x2f\x5c]/i', // ../ or ..\ 88 | '/\x{ff0e}\x{ff0e}[\x{ff0f}\x{ff3c}]/u', // Fullwidth .. + slash or backslash 89 | '/\x{2215}/u', // Unicode division slash 90 | '/\xef\xbc\x8f/', // Fullwidth solidus 91 | '/\xc0\xaf/', // Overlong UTF-8 / 92 | ]; 93 | $cleaned = preg_replace($patterns, '', $normalized); 94 | 95 | // 4. Remove null bytes and control characters 96 | $cleaned = preg_replace('/[\x00-\x1f\x7f]/', '', $cleaned); 97 | 98 | return trim($cleaned); 99 | } 100 | } -------------------------------------------------------------------------------- /modules/wizard/Controllers/Install.php: -------------------------------------------------------------------------------- 1 | container = $container; 34 | } 35 | 36 | public function __invoke(Request $request, Response $response): Response 37 | { 38 | try { 39 | $configfile = $this->container->get('settings_root') .'/config'; 40 | $fileconf = $configfile . DIRECTORY_SEPARATOR .'settings.php'; 41 | $post = $request->getParsedBody(); 42 | $config = new Config($fileconf); 43 | $resultc = json_decode($config->base($post)); 44 | if ((int) $resultc->code != 0) { 45 | throw new Exception($resultc->msg, $resultc->code); 46 | } 47 | if ($post['driver'] == 'mysql') { 48 | $installsqldb = new InstallSQLDB($fileconf); 49 | $resultdb = json_decode($installsqldb->createDB($post['driver'], $post['dbhost'], $post['dbname'], $post['dbuser'], $post['dbpass'], $post['prefix'], (string) $post['dbencoding'], (string) $post['dbcollation'], (int) (!empty($post['dbport']) ? $post['dbport'] : 3306), (string) $post['admin'], (string) $post['email'], (string) $post['password'])); 50 | if ((int) $resultdb->code != 0) { 51 | throw new Exception($resultdb->msg, (int) $resultdb->code); 52 | } 53 | } 54 | if ($post['driver'] == 'pgsql') { 55 | $installpgsqldb = new InstallPGSQLDB($fileconf); 56 | $resultdb = json_decode($installpgsqldb->createDB($post['driver'], $post['dbhost'], $post['dbname'], $post['dbuser'], $post['dbpass'], $post['prefix'], $post['dbencoding'], (int) (!empty($post['dbport'])? $post['dbport'] : 5432))); 57 | if ((int) $resultdb->code != 0) { 58 | throw new Exception($resultdb->msg, (int) $resultdb->code); 59 | } 60 | } 61 | elseif ($post['driver'] == 'sqlite') { 62 | $installsqlitedb = new InstallSQLiteDB($fileconf, $this->container->get('settings_root')); 63 | $resultdb = json_decode($installsqlitedb->createDB($post['driver'], $post['dbfile'], $post['prefix'])); 64 | if ((int) $resultdb->code != 0) { 65 | throw new Exception($resultdb->msg, (int) $resultdb->code); 66 | } 67 | } 68 | session_destroy(); 69 | $result = array( 70 | 'code' => '0', 71 | 'msg' => 'OK' 72 | ); 73 | } catch (Exception $e) { 74 | $result = array( 75 | 'code' => $e->getCode(), 76 | 'msg' => $e->getMessage() 77 | ); 78 | } 79 | $response->getBody()->write(json_encode($result)); 80 | return $response; 81 | } 82 | } -------------------------------------------------------------------------------- /templates/default/wizard_charset.json: -------------------------------------------------------------------------------- 1 | { 2 | "mysql": [ 3 | {"value": "ucs2", "label": "ucs2"}, 4 | {"value": "big5", "label": "big5"}, 5 | {"value": "latin1", "label": "latin1"}, 6 | {"value": "latin2", "label": "latin2"}, 7 | {"value": "latin3", "label": "latin3"}, 8 | {"value": "latin4", "label": "latin4"}, 9 | {"value": "latin5", "label": "latin5"}, 10 | {"value": "latin6", "label": "latin6"}, 11 | {"value": "latin7", "label": "latin7"}, 12 | {"value": "binary", "label": "binary"}, 13 | {"value": "utf8", "label": "utf8"}, 14 | {"value": "utf8mb4", "label": "utf8mb4", "selected": true} 15 | ], 16 | "mssql": [ 17 | {"value": "utf8", "label": "utf8", "selected": true}, 18 | {"value": "latin1", "label": "latin1"} 19 | ], 20 | "pgsql": [ 21 | {"value": "BIG5", "label": "BIG5"}, 22 | {"value": "EUC_CN", "label": "EUC_CN"}, 23 | {"value": "EUC_JP", "label": "EUC_JP"}, 24 | {"value": "EUC_JIS_2004", "label": "EUC_JIS_2004"}, 25 | {"value": "EUC_KR", "label": "EUC_KR"}, 26 | {"value": "EUC_TW", "label": "EUC_TW"}, 27 | {"value": "GB18030", "label": "GB18030"}, 28 | {"value": "GBK", "label": "GBK"}, 29 | {"value": "ISO_8859_5", "label": "ISO_8859_5"}, 30 | {"value": "ISO_8859_6", "label": "ISO_8859_6"}, 31 | {"value": "ISO_8859_7", "label": "ISO_8859_7"}, 32 | {"value": "ISO_8859_8", "label": "ISO_8859_8"}, 33 | {"value": "JOHAB", "label": "JOHAB"}, 34 | {"value": "KOI8R", "label": "KOI8R"}, 35 | {"value": "KOI8U", "label": "KOI8U"}, 36 | {"value": "LATIN1", "label": "LATIN1"}, 37 | {"value": "LATIN2", "label": "LATIN2"}, 38 | {"value": "LATIN3", "label": "LATIN3"}, 39 | {"value": "LATIN4", "label": "LATIN4"}, 40 | {"value": "LATIN5", "label": "LATIN5"}, 41 | {"value": "LATIN6", "label": "LATIN6"}, 42 | {"value": "LATIN7", "label": "LATIN7"}, 43 | {"value": "LATIN8", "label": "LATIN8"}, 44 | {"value": "LATIN9", "label": "LATIN9"}, 45 | {"value": "LATIN10", "label": "LATIN10"}, 46 | {"value": "MULE_INTERNAL", "label": "MULE_INTERNAL"}, 47 | {"value": "SJIS", "label": "SJIS"}, 48 | {"value": "SHIFT_JIS_2004", "label": "SHIFT_JIS_2004"}, 49 | {"value": "SQL_ASCII", "label": "SQL_ASCII"}, 50 | {"value": "UHC", "label": "UHC"}, 51 | {"value": "UTF8", "label": "UTF8", "selected": true}, 52 | {"value": "WIN866", "label": "WIN866"}, 53 | {"value": "WIN874", "label": "WIN874"}, 54 | {"value": "WIN1250", "label": "WIN1250"}, 55 | {"value": "WIN1251", "label": "WIN1251"}, 56 | {"value": "WIN1252", "label": "WIN1252"}, 57 | {"value": "WIN1253", "label": "WIN1253"}, 58 | {"value": "WIN1254", "label": "WIN1254"}, 59 | {"value": "WIN1255", "label": "WIN1255"}, 60 | {"value": "WIN1256", "label": "WIN1256"}, 61 | {"value": "WIN1257", "label": "WIN1257"}, 62 | {"value": "WIN1258", "label": "WIN1258"} 63 | ], 64 | "sqlite": [ 65 | {"value": "UTF8", "label": "UTF8", "selected": true}, 66 | {"value": "ISO-8859-1", "label": "ISO-8859-1"} 67 | ], 68 | "nodb": [] 69 | } -------------------------------------------------------------------------------- /templates/default/js/editorjs/warning.umd.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(`.cdx-warning{position:relative}@media all and (min-width: 736px){.cdx-warning{padding-left:36px}}.cdx-warning [contentEditable=true][data-placeholder]:before{position:absolute;content:attr(data-placeholder);color:#707684;font-weight:400;opacity:0}.cdx-warning [contentEditable=true][data-placeholder]:empty:before{opacity:1}.cdx-warning [contentEditable=true][data-placeholder]:empty:focus:before{opacity:0}.cdx-warning:before{content:"";background-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='5' y='5' width='14' height='14' rx='4' stroke='black' stroke-width='2'/%3E%3Cline x1='12' y1='9' x2='12' y2='12' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M12 15.02V15.01' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");width:24px;height:24px;background-size:24px 24px;position:absolute;margin-top:8px;left:0}@media all and (max-width: 735px){.cdx-warning:before{display:none}}.cdx-warning__message{min-height:85px}.cdx-warning__title{margin-bottom:6px}`)),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})(); 2 | (function(r,n){typeof exports=="object"&&typeof module<"u"?module.exports=n():typeof define=="function"&&define.amd?define(n):(r=typeof globalThis<"u"?globalThis:r||self,r.Warning=n())})(this,function(){"use strict";const r='',n="";class a{static get isReadOnlySupported(){return!0}static get toolbox(){return{icon:r,title:"Warning"}}static get enableLineBreaks(){return!0}static get DEFAULT_TITLE_PLACEHOLDER(){return"Title"}static get DEFAULT_MESSAGE_PLACEHOLDER(){return"Message"}get CSS(){return{baseClass:this.api.styles.block,wrapper:"cdx-warning",title:"cdx-warning__title",input:this.api.styles.input,message:"cdx-warning__message"}}constructor({data:t,config:e,api:s,readOnly:i}){this.api=s,this.readOnly=i,this.titlePlaceholder=(e==null?void 0:e.titlePlaceholder)||a.DEFAULT_TITLE_PLACEHOLDER,this.messagePlaceholder=(e==null?void 0:e.messagePlaceholder)||a.DEFAULT_MESSAGE_PLACEHOLDER,this.data={title:t.title||"",message:t.message||""}}render(){const t=this._make("div",[this.CSS.baseClass,this.CSS.wrapper]),e=this._make("div",[this.CSS.input,this.CSS.title],{contentEditable:!this.readOnly,innerHTML:this.data.title}),s=this._make("div",[this.CSS.input,this.CSS.message],{contentEditable:!this.readOnly,innerHTML:this.data.message});return e.dataset.placeholder=this.titlePlaceholder,s.dataset.placeholder=this.messagePlaceholder,t.appendChild(e),t.appendChild(s),t}save(t){const e=t.querySelector(`.${this.CSS.title}`),s=t.querySelector(`.${this.CSS.message}`);return Object.assign(this.data,{title:(e==null?void 0:e.innerHTML)??"",message:(s==null?void 0:s.innerHTML)??""})}_make(t,e=null,s={}){const i=document.createElement(t);Array.isArray(e)?i.classList.add(...e):e&&i.classList.add(e);for(const l in s)i[l]=s[l];return i}static get sanitize(){return{title:{},message:{}}}}return a}); 3 | -------------------------------------------------------------------------------- /modules/admin/Controllers/CLogin.php: -------------------------------------------------------------------------------- 1 | app = $app; 26 | $this->container = $container; 27 | $this->session = $session; 28 | $this->login = new MLogin($container->get('db'), $session); 29 | $this->settingcookie = $container->get('settings_cookie'); 30 | $this->settings = $container->has('settings_global') ? $container->get('settings_global') : []; 31 | } 32 | 33 | public function __invoke(Request $request, Response $response): Response 34 | { 35 | $post = $request->getParsedBody(); 36 | $username = (string) (!empty($post['username']) ? trim($post['username']) : ''); 37 | $password = (string) (!empty($post['password']) ? trim($post['password']) : ''); 38 | 39 | // Verify credentials using MLogin 40 | $result = $this->login->verify($username, $password); 41 | $resultData = json_decode($result, true); 42 | 43 | // If login successful, generate JWT and set cookie 44 | if ($resultData['success']) { 45 | // Get session data set by MLogin 46 | $userId = $this->session->get('admin.id'); 47 | $ulid = $this->session->get('admin.ulid'); 48 | $permissionsJson = $this->session->get('admin.permissions'); 49 | 50 | // Parse permissions JSON to array 51 | $permissions = json_decode($permissionsJson, true) ?? []; 52 | $permissionSlugs = array_column($permissions, 'slug'); 53 | 54 | // Generate JWT token with ulid and permissions 55 | $secureSettings = $this->container->has('settings_secure') ? $this->container->get('settings_secure') : []; 56 | $jwtHelper = new JWTHelper($secureSettings); 57 | $token = $jwtHelper->generateToken([ 58 | 'ulid' => $ulid, 59 | 'permissions' => $permissionSlugs 60 | ]); 61 | 62 | // Set JWT cookie using Slim PSR-7 Cookies 63 | $cookies = new Cookies(); 64 | $cookies->set($this->settingcookie['admin'], [ 65 | 'value' => $token, 66 | 'expires' => time() + $this->settingcookie['cookie_exp'], 67 | 'path' => $this->settingcookie['cookie_path'], 68 | 'secure' => $this->settingcookie['cookie_secure'], 69 | 'httponly' => $this->settingcookie['cookie_http'], 70 | 'samesite' => $this->settingcookie['cookie_samesite'] 71 | ]); 72 | 73 | // Apply cookies to response 74 | foreach ($cookies->toHeaders() as $header) { 75 | $response = $response->withAddedHeader('Set-Cookie', $header); 76 | } 77 | 78 | // Add redirect URL to response 79 | $basepath = (string) $this->app->getBasePath(); 80 | $adminpath = $this->settings['adminpath'] ? ($this->settings['locale'] ? $request->getAttribute('language') .'/'. $this->settings['adminpath'] : $this->settings['adminpath']) : 'admin'; 81 | $resultData['redirect'] = $basepath . '/' . $adminpath; 82 | } 83 | 84 | $response->getBody()->write(json_encode($resultData)); 85 | return $response->withHeader('Content-Type', 'application/json'); 86 | } 87 | } -------------------------------------------------------------------------------- /modules/wizard/languages/it.lng: -------------------------------------------------------------------------------- 1 | {"body":{"title":"Benvenuto in","l_welcome":"Benvenuto in","l_wizard_1":"Il wizard ti guider\u00e0 nella configurazione delle impostazioni del CMS","l_wizard_2_1":"Parametri di accesso al DB","l_wizard_3_1":"Test Parametri Connessione DB","l_wizard_4_1":"Parametri Configurazione CMS (Lasciare in default se non si \u00e8 sicuri)","l_wizard_5_1":"Configura dati di accesso amministratore","l_wizard_6_1":"Procedi all' installazione del CMS","l_check_perm":"Controllo Permessi Di Scrittura Cartella:","l_check_perm_ok":"Permessi di scrittura corretti","l_check_perm_no":"Verifica i permessi della cartella","l_check_openssl":"Controllo Estensione Openssl Attiva:","l_check_openssl_ok":"Estensione Openssl Attiva","l_check_openssl_no":"Openssl non \u00e8 attiva, impossibile continuare","l_check_db_ok":"Connessione effettuata con successo","l_check_db_no":"Connessione fallita:","l_dbtype":"Tipo DB:","l_dbtype_ph":"Scegli tipo DB","l_dbfile":"DB File:","l_dbfile_ph":"Nome DB SQLite:","l_dbhost":"Host:","l_dbhost_ph":"Inserisci Indirizzo DB","l_dbport":"Porta:","l_dbport_ph":"Inserisci Porta DB (lascia vuoto per default)","l_dbname":"Nome DB:","l_dbname_ph":"Inserisci Nome DB","l_dbuser":"Nome Utente DB:","l_dbuser_ph":"Inserisci Nome Utente DB","l_dbpass":"Password DB:","l_dbpass_ph":"Inserisci Password DB","l_dbencoding":"Encoding DB:","l_dbencoding_ph":"Inserisci Encoding DB","l_dbcollation":"Collation DB:","l_dbcollation_ph":"Inserisci Collation DB","l_title":"Nome Sito Web","l_title_ph":"Inserisci Nome Sito Web","l_prefix_tb":"Prefisso Tabelle","l_prefix_tb_ph":"Inserisci Prefisso Tabelle","l_alias_admin":"Alias Admin","l_alias_admin_ph":"Inserisci un alias per pannello admin","l_prefix_salt":"Stringa Salt Password","l_prefix_salt_ph":"Genera La Stringa Utilizzata Per Criptare le Password","l_maintenance":"Password Manutenzione","l_maintenance_ph":"Genera La Password Per la Manutenzione","l_template_default":"Template Predefinito","l_locale_url":"Lingua in URL","l_yes":"SI","l_no":"NO","l_encoding_page":"Encoding Pagine","l_encoding_page_ph":"Codifica delle pagine html","l_adm_cookname":"Prefisso Cookie Admin","l_adm_cookname_ph":"Inserisci Prefisso Cookie Admin","l_usr_cookname":"Prefisso Cookie Utente","l_usr_cookname_ph":"Inserisci Prefisso Cookie Utente","l_cookie_expire":"Durata Cookie (Sec.)","l_cookie_expire_ph":"Inserisci Secondi Durata Cookie","l_cookie_max_expire":"Max Durata Cookie (Sec.)","l_cookie_max_expire_ph":"Inserisci Secondi Max Durata Cookie","l_cookie_path":"Path del Cookie","l_cookie_path_ph":"Inserisci Path del Cookie","l_app_name":"Nome App","l_app_name_ph":"Inserisci Nome Applicazione","l_prefix_fname":"Prefisso File Log","l_prefix_fname_ph":"Inserisci Prefisso File Log","l_facebook_app":"Facebook APP","l_facebook_app_ph":"Inserisci Facebook APP Key","l_facebook_secret":"Facebook Secret","l_facebook_secret_ph":"Inserisci Facebook Secret Key","l_google_key":"Google API Key","l_google_key_ph":"Inserisci Google API Key","l_google_secret":"Google Secret Key","l_google_secret_ph":"Inserisci Google Secret Key","l_username":"Username Amministratore","l_username_ph":"Inserisci Username Amministratore","l_email":"Email Amministratore","l_email_ph":"Inserisci Email Amministratore","l_password":"Password Amministratore","l_password_ph":"Inserisci Password Amministratore","l_language":"Lingua","l_lang_default":"Lingua Predefinita","l_generate":"Genera","l_install_ok":"Installazione Completata","l_install_ko":"Installazione Fallita","l_start":"Inizio","l_second":"Database","l_third":"Test Parametri","l_forth":"Configura","l_fifth":"Dati Login","l_sixth":"Installa","l_previous":"Precedente","l_next":"Successivo","l_install":"Installa"}} -------------------------------------------------------------------------------- /templates/default/js/editorjs/toolbox.d.ts: -------------------------------------------------------------------------------- 1 | import { default as Popover } from './utils/popover'; 2 | /** 3 | * @typedef {object} PopoverItem 4 | * @property {string} label - button text 5 | * @property {string} icon - button icon 6 | * @property {boolean} confirmationRequired - if true, a confirmation state will be applied on the first click 7 | * @property {function} hideIf - if provided, item will be hid, if this method returns true 8 | * @property {function} onClick - click callback 9 | */ 10 | /** 11 | * Toolbox is a menu for manipulation of rows/cols 12 | * 13 | * It contains toggler and Popover: 14 | * 15 | * 16 | * 17 | * 18 | */ 19 | export default class Toolbox { 20 | /** 21 | * Style classes 22 | */ 23 | static get CSS(): { 24 | toolbox: string; 25 | toolboxShowed: string; 26 | toggler: string; 27 | }; 28 | /** 29 | * Creates toolbox buttons and toolbox menus 30 | * 31 | * @param {Object} config 32 | * @param {any} config.api - Editor.js api 33 | * @param {PopoverItem[]} config.items - Editor.js api 34 | * @param {function} config.onOpen - callback fired when the Popover is opening 35 | * @param {function} config.onClose - callback fired when the Popover is closing 36 | * @param {string} config.cssModifier - the modifier for the Toolbox. Allows to add some specific styles. 37 | */ 38 | constructor({ api, items, onOpen, onClose, cssModifier }: { 39 | api: any; 40 | items: PopoverItem[]; 41 | onOpen: Function; 42 | onClose: Function; 43 | cssModifier: string; 44 | }); 45 | api: any; 46 | items: PopoverItem[]; 47 | onOpen: Function; 48 | onClose: Function; 49 | cssModifier: string; 50 | popover: Popover; 51 | wrapper: Element; 52 | /** 53 | * Returns rendered Toolbox element 54 | */ 55 | get element(): Element; 56 | /** 57 | * Creating a toolbox to open menu for a manipulating columns 58 | * 59 | * @returns {Element} 60 | */ 61 | createToolbox(): Element; 62 | /** 63 | * Creates the Toggler 64 | * 65 | * @returns {Element} 66 | */ 67 | createToggler(): Element; 68 | /** 69 | * Creates the Popover instance and render it 70 | * 71 | * @returns {Element} 72 | */ 73 | createPopover(): Element; 74 | /** 75 | * Toggler click handler. Opens/Closes the popover 76 | * 77 | * @returns {void} 78 | */ 79 | togglerClicked(): void; 80 | /** 81 | * Shows the Toolbox 82 | * 83 | * @param {function} computePositionMethod - method that returns the position coordinate 84 | * @returns {void} 85 | */ 86 | show(computePositionMethod: Function): void; 87 | /** 88 | * Hides the Toolbox 89 | * 90 | * @returns {void} 91 | */ 92 | hide(): void; 93 | } 94 | export type PopoverItem = { 95 | /** 96 | * - button text 97 | */ 98 | /** 99 | * - button text 100 | */ 101 | label: string; 102 | /** 103 | * - button icon 104 | */ 105 | /** 106 | * - button icon 107 | */ 108 | icon: string; 109 | /** 110 | * - if true, a confirmation state will be applied on the first click 111 | */ 112 | /** 113 | * - if true, a confirmation state will be applied on the first click 114 | */ 115 | confirmationRequired: boolean; 116 | /** 117 | * - if provided, item will be hid, if this method returns true 118 | */ 119 | /** 120 | * - if provided, item will be hid, if this method returns true 121 | */ 122 | hideIf: Function; 123 | /** 124 | * - click callback 125 | */ 126 | /** 127 | * - click callback 128 | */ 129 | onClick: Function; 130 | }; 131 | -------------------------------------------------------------------------------- /templates/default/css/404.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Nunito+Sans'); 2 | :root { 3 | --blue: #0e0620; 4 | --white: #fff; 5 | --green: #2ccf6d; 6 | } 7 | html, 8 | body { 9 | height: 100%; 10 | } 11 | body { 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | font-family:"Nunito Sans"; 16 | color: var(--blue); 17 | font-size: 1em; 18 | } 19 | button { 20 | font-family:"Nunito Sans"; 21 | } 22 | ul { 23 | list-style-type: none; 24 | padding-inline-start: 35px; 25 | } 26 | svg { 27 | width: 100%; 28 | visibility: hidden; 29 | } 30 | h1 { 31 | font-size: 7.5em; 32 | margin: 15px 0px; 33 | font-weight:bold; 34 | } 35 | h2 { 36 | font-weight:bold; 37 | } 38 | .hamburger-menu { 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | padding: 35px; 43 | z-index: 2; 44 | 45 | & button { 46 | position: relative; 47 | width: 30px; 48 | height: 22px; 49 | border: none; 50 | background: none; 51 | padding: 0; 52 | cursor: pointer; 53 | 54 | & span { 55 | position: absolute; 56 | height: 3px; 57 | background: #000; 58 | width: 100%; 59 | left: 0px; 60 | top: 0px; 61 | transition: 0.1s ease-in; 62 | &:nth-child(2) { 63 | top: 9px; 64 | } 65 | &:nth-child(3) { 66 | top: 18px; 67 | } 68 | } 69 | } 70 | & [data-state="open"] { 71 | & span { 72 | &:first-child { 73 | transform: rotate(45deg); 74 | top: 10px; 75 | } 76 | &:nth-child(2) { 77 | width: 0%; 78 | opacity:0; 79 | } 80 | &:nth-child(3) { 81 | transform: rotate(-45deg); 82 | top: 10px; 83 | } 84 | } 85 | } 86 | } 87 | nav { 88 | position: absolute; 89 | height: 100%; 90 | top: 0; 91 | left: 0; 92 | background: var(--green); 93 | color: var(--blue); 94 | width: 300px; 95 | z-index: 1; 96 | padding-top: 80px; 97 | transform: translateX(-100%); 98 | transition: 0.24s cubic-bezier(.52,.01,.8,1); 99 | & li { 100 | transform: translateX(-5px); 101 | transition: 0.16s cubic-bezier(0.44, 0.09, 0.46, 0.84); 102 | opacity: 0; 103 | } 104 | & a { 105 | display: block; 106 | font-size: 1.75em; 107 | font-weight: bold; 108 | text-decoration: none; 109 | color: inherit; 110 | transition: 0.24s ease-in-out; 111 | &:hover { 112 | text-decoration: none; 113 | color: var(--white); 114 | } 115 | } 116 | &[data-state="open"] { 117 | transform: translateX(0%); 118 | & ul { 119 | @for $i from 1 through 4 { 120 | li:nth-child(#{$i}) { 121 | transition-delay: 0.16s * $i; 122 | transform: translateX(0px); 123 | opacity: 1; 124 | } 125 | } 126 | } 127 | } 128 | } 129 | .btn { 130 | z-index: 1; 131 | overflow: hidden; 132 | background: transparent; 133 | position: relative; 134 | padding: 8px 50px; 135 | border-radius: 30px; 136 | cursor: pointer; 137 | font-size: 1em; 138 | letter-spacing: 2px; 139 | transition: 0.2s ease; 140 | font-weight: bold; 141 | margin: 5px 0px; 142 | &.green { 143 | border: 4px solid var(--green); 144 | color: var(--blue); 145 | &:before { 146 | content: ""; 147 | position: absolute; 148 | left: 0; 149 | top: 0; 150 | width: 0%; 151 | height: 100%; 152 | background: var(--green); 153 | z-index: -1; 154 | transition: 0.2s ease; 155 | } 156 | &:hover { 157 | color: var(--white); 158 | background: var(--green); 159 | transition: 0.2s ease; 160 | &:before { 161 | width: 100%; 162 | } 163 | } 164 | } 165 | } 166 | @media screen and (max-width:768px) { 167 | body { 168 | display:block; 169 | } 170 | .container { 171 | margin-top:70px; 172 | margin-bottom:70px; 173 | } 174 | } -------------------------------------------------------------------------------- /modules/wizard/Classes/InstallPGSQLDB.php: -------------------------------------------------------------------------------- 1 | fileconf = $fileconf; 15 | } 16 | 17 | public function createDB(string $driver, string $dbhost, string $dbname, string $dbuser, string $dbpass, string $prefix, string $charset, int $dbport = 3306): string { 18 | try { 19 | $configfile = (file_exists($this->fileconf) ? $this->fileconf : ''); 20 | $content = file_get_contents($configfile); 21 | $dbSettings = << [ 24 | 'default' => [ 25 | 'type' => '$driver', 26 | 'host' => '$dbhost', 27 | 'database' => '$dbname', 28 | 'username' => '$dbuser', 29 | 'password' => '$dbpass', 30 | 'prefix' => '$prefix', 31 | 'charset' => '$charset', 32 | 'port' => $dbport 33 | ]\n\t 34 | PHP; 35 | if (preg_match_all('/\]\s*\];/s', $content, $matches, PREG_OFFSET_CAPTURE)) { 36 | $pos = end(end($matches[0])); 37 | $newContent = substr_replace($content, $dbSettings, $pos, 0); 38 | file_put_contents($configfile, $newContent); 39 | } else { 40 | throw new Exception("Error edit config file", 3); 41 | } 42 | $db = new DB([ 43 | 'type' => $driver, 44 | 'host' => $dbhost, 45 | 'database' => $dbname, 46 | 'username' => $dbuser, 47 | 'password' => $dbpass, 48 | 'prefix' => $prefix, 49 | 'charset' => $charset, 50 | 'port' => $dbport, 51 | 'error' => \PDO::ERRMODE_EXCEPTION 52 | ]); 53 | $db->create("admins", [ 54 | "id" => [ 55 | "SERIAL", 56 | "PRIMARY KEY" 57 | ], 58 | "user" => [ 59 | "VARCHAR(100)", 60 | "NOT NULL", 61 | "UNIQUE" 62 | ], 63 | "pass" => [ 64 | "VARCHAR(255)", 65 | "NOT NULL" 66 | ], 67 | "email" => [ 68 | "VARCHAR(255)", 69 | "NOT NULL", 70 | "UNIQUE" 71 | ], 72 | "superuser" => [ 73 | "VARCHAR(255)", 74 | "NULL" 75 | ], 76 | "type" => [ 77 | "SMALLINT", 78 | "NULL" 79 | ], 80 | "stato" => [ 81 | "SMALLINT", 82 | "NOT NULL", 83 | "DEFAULT", 84 | 1 85 | ] 86 | ]); 87 | $db->create("cookies", [ 88 | "id" => [ 89 | "SERIAL", 90 | "PRIMARY KEY" 91 | ], 92 | "uid" => [ 93 | "INTEGER", 94 | "NOT NULL" 95 | ], 96 | "uuid" => [ 97 | "VARCHAR(255)", 98 | "NOT NULL" 99 | ], 100 | "type" => [ 101 | "VARCHAR(10)", 102 | "NOT NULL" 103 | ], 104 | "auth_token" => [ 105 | "VARCHAR(255)", 106 | "NOT NULL" 107 | ], 108 | "lastseen" => [ 109 | "TIMESTAMP", 110 | "NOT NULL", 111 | "DEFAULT", 112 | "CURRENT_TIMESTAMP" 113 | ] 114 | ]); 115 | $db->create("routes", [ 116 | "id" => [ 117 | "SERIAL", 118 | "PRIMARY KEY" 119 | ], 120 | "request" => [ 121 | "VARCHAR(255)", 122 | "NOT NULL" 123 | ], 124 | "dest" => [ 125 | "VARCHAR(255)", 126 | "NOT NULL" 127 | ], 128 | "type" => [ 129 | "SMALLINT", 130 | "NOT NULL", 131 | "DEFAULT", 132 | 1 133 | ], 134 | "redirect" => [ 135 | "INTEGER", 136 | "NOT NULL", 137 | "DEFAULT", 138 | 301 139 | ], 140 | "canonical" => [ 141 | "SMALLINT", 142 | "NOT NULL", 143 | "DEFAULT", 144 | 0 145 | ] 146 | ]); 147 | $result = array( 148 | 'code' => '0', 149 | 'msg' => 'OK' 150 | ); 151 | } catch (Exception $e) { 152 | $result = array( 153 | 'code' => $e->getCode(), 154 | 'msg' => $e->getMessage() 155 | ); 156 | } 157 | return json_encode($result); 158 | } 159 | } -------------------------------------------------------------------------------- /templates/default/css/500.css: -------------------------------------------------------------------------------- 1 | /**/ 2 | :root { 3 | --main-color: #eaeaea; 4 | --stroke-color: black; 5 | 6 | } 7 | /**/ 8 | body { 9 | background: var(--main-color); 10 | } 11 | h1 { 12 | margin: 100px auto 0 auto; 13 | color: var(--stroke-color); 14 | font-family: 'Encode Sans Semi Condensed', Verdana, sans-serif; 15 | font-size: 10rem; line-height: 10rem; 16 | font-weight: 200; 17 | text-align: center; 18 | } 19 | h2 { 20 | margin: 20px auto 30px auto; 21 | font-family: 'Encode Sans Semi Condensed', Verdana, sans-serif; 22 | font-size: 1.5rem; 23 | font-weight: 200; 24 | text-align: center; 25 | } 26 | h1, h2 { 27 | -webkit-transition: opacity 0.5s linear, margin-top 0.5s linear; /* Safari */ 28 | transition: opacity 0.5s linear, margin-top 0.5s linear; 29 | } 30 | .loading h1, .loading h2 { 31 | margin-top: 0px; 32 | opacity: 0; 33 | } 34 | .gears { 35 | position: relative; 36 | margin: 0 auto; 37 | width: auto; height: 0; 38 | } 39 | .gear { 40 | position: relative; 41 | z-index: 0; 42 | width: 120px; height: 120px; 43 | margin: 0 auto; 44 | border-radius: 50%; 45 | background: var(--stroke-color); 46 | } 47 | .gear:before{ 48 | position: absolute; left: 5px; top: 5px; right: 5px; bottom: 5px; 49 | z-index: 2; 50 | content: ""; 51 | border-radius: 50%; 52 | background: var(--main-color); 53 | } 54 | .gear:after { 55 | position: absolute; left: 25px; top: 25px; 56 | z-index: 3; 57 | content: ""; 58 | width: 70px; height: 70px; 59 | border-radius: 50%; 60 | border: 5px solid var(--stroke-color); 61 | box-sizing: border-box; 62 | background: var(--main-color); 63 | } 64 | .gear.one { 65 | left: -130px; 66 | } 67 | .gear.two { 68 | top: -75px; 69 | } 70 | .gear.three { 71 | top: -235px; 72 | left: 130px; 73 | } 74 | .gear .bar { 75 | position: absolute; left: -15px; top: 50%; 76 | z-index: 0; 77 | width: 150px; height: 30px; 78 | margin-top: -15px; 79 | border-radius: 5px; 80 | background: var(--stroke-color); 81 | } 82 | .gear .bar:before { 83 | position: absolute; left: 5px; top: 5px; right: 5px; bottom: 5px; 84 | z-index: 1; 85 | content: ""; 86 | border-radius: 2px; 87 | background: var(--main-color); 88 | } 89 | .gear .bar:nth-child(2) { 90 | transform: rotate(60deg); 91 | -webkit-transform: rotate(60deg); 92 | } 93 | .gear .bar:nth-child(3) { 94 | transform: rotate(120deg); 95 | -webkit-transform: rotate(120deg); 96 | } 97 | @-webkit-keyframes clockwise { 98 | 0% { -webkit-transform: rotate(0deg);} 99 | 100% { -webkit-transform: rotate(360deg);} 100 | } 101 | @-webkit-keyframes anticlockwise { 102 | 0% { -webkit-transform: rotate(360deg);} 103 | 100% { -webkit-transform: rotate(0deg);} 104 | } 105 | @-webkit-keyframes clockwiseError { 106 | 0% { -webkit-transform: rotate(0deg);} 107 | 20% { -webkit-transform: rotate(30deg);} 108 | 40% { -webkit-transform: rotate(25deg);} 109 | 60% { -webkit-transform: rotate(30deg);} 110 | 100% { -webkit-transform: rotate(0deg);} 111 | } 112 | @-webkit-keyframes anticlockwiseErrorStop { 113 | 0% { -webkit-transform: rotate(0deg);} 114 | 20% { -webkit-transform: rotate(-30deg);} 115 | 60% { -webkit-transform: rotate(-30deg);} 116 | 100% { -webkit-transform: rotate(0deg);} 117 | } 118 | @-webkit-keyframes anticlockwiseError { 119 | 0% { -webkit-transform: rotate(0deg);} 120 | 20% { -webkit-transform: rotate(-30deg);} 121 | 40% { -webkit-transform: rotate(-25deg);} 122 | 60% { -webkit-transform: rotate(-30deg);} 123 | 100% { -webkit-transform: rotate(0deg);} 124 | } 125 | .gear.one { 126 | -webkit-animation: anticlockwiseErrorStop 2s linear infinite; 127 | } 128 | .gear.two { 129 | -webkit-animation: anticlockwiseError 2s linear infinite; 130 | } 131 | .gear.three { 132 | -webkit-animation: clockwiseError 2s linear infinite; 133 | } 134 | .loading .gear.one, .loading .gear.three { 135 | -webkit-animation: clockwise 3s linear infinite; 136 | } 137 | .loading .gear.two { 138 | -webkit-animation: anticlockwise 3s linear infinite; 139 | } -------------------------------------------------------------------------------- /templates/default/css/loader.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | font-size: 19px; 3 | margin: 5em auto; 4 | width: 1em; 5 | height: 1em; 6 | border-radius: 50%; 7 | position: relative; 8 | text-indent: -9999em; 9 | -webkit-animation: load 1.3s infinite linear; 10 | animation: load 1.3s infinite linear; 11 | } 12 | @-webkit-keyframes load { 13 | 0%, 14 | 100% { 15 | box-shadow: 0em -3em 0em 0.2em #518ec2, 2em -2em 0 0em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 0em #518ec2; 16 | } 17 | 12.5% { 18 | box-shadow: 0em -3em 0em 0em #518ec2, 2em -2em 0 0.2em #518ec2, 3em 0em 0 0em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 19 | } 20 | 25% { 21 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 0em #518ec2, 3em 0em 0 0.2em #518ec2, 2em 2em 0 0em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 22 | } 23 | 37.5% { 24 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 0em #518ec2, 2em 2em 0 0.2em #518ec2, 0em 3em 0 0em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 25 | } 26 | 50% { 27 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 0em #518ec2, 0em 3em 0 0.2em #518ec2, -2em 2em 0 0em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 28 | } 29 | 62.5% { 30 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 0em #518ec2, -2em 2em 0 0.2em #518ec2, -3em 0em 0 0em #518ec2, -2em -2em 0 -0.5em #518ec2; 31 | } 32 | 75% { 33 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 0em #518ec2, -3em 0em 0 0.2em #518ec2, -2em -2em 0 0em #518ec2; 34 | } 35 | 87.5% { 36 | box-shadow: 0em -3em 0em 0em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 0em #518ec2, -3em 0em 0 0em #518ec2, -2em -2em 0 0.2em #518ec2; 37 | } 38 | } 39 | @keyframes load { 40 | 0%, 41 | 100% { 42 | box-shadow: 0em -3em 0em 0.2em #518ec2, 2em -2em 0 0em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 0em #518ec2; 43 | } 44 | 12.5% { 45 | box-shadow: 0em -3em 0em 0em #518ec2, 2em -2em 0 0.2em #518ec2, 3em 0em 0 0em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 46 | } 47 | 25% { 48 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 0em #518ec2, 3em 0em 0 0.2em #518ec2, 2em 2em 0 0em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 49 | } 50 | 37.5% { 51 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 0em #518ec2, 2em 2em 0 0.2em #518ec2, 0em 3em 0 0em #518ec2, -2em 2em 0 -0.5em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 52 | } 53 | 50% { 54 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 0em #518ec2, 0em 3em 0 0.2em #518ec2, -2em 2em 0 0em #518ec2, -3em 0em 0 -0.5em #518ec2, -2em -2em 0 -0.5em #518ec2; 55 | } 56 | 62.5% { 57 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 0em #518ec2, -2em 2em 0 0.2em #518ec2, -3em 0em 0 0em #518ec2, -2em -2em 0 -0.5em #518ec2; 58 | } 59 | 75% { 60 | box-shadow: 0em -3em 0em -0.5em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 0em #518ec2, -3em 0em 0 0.2em #518ec2, -2em -2em 0 0em #518ec2; 61 | } 62 | 87.5% { 63 | box-shadow: 0em -3em 0em 0em #518ec2, 2em -2em 0 -0.5em #518ec2, 3em 0em 0 -0.5em #518ec2, 2em 2em 0 -0.5em #518ec2, 0em 3em 0 -0.5em #518ec2, -2em 2em 0 0em #518ec2, -3em 0em 0 0em #518ec2, -2em -2em 0 0.2em #518ec2; 64 | } 65 | } 66 | .adjust 67 | {min-height:60px; height:auto;} 68 | .mt 69 | { margin-top:50px;} -------------------------------------------------------------------------------- /core/perseo/Handlers/ShutdownHandler.php: -------------------------------------------------------------------------------- 1 | request = $request; 52 | $this->errorHandler = $errorHandler; 53 | $this->displayErrorDetails = $displayErrorDetails; 54 | $this->logger = $logger; 55 | $this->settings = ($container->has('settings_error') ? $container->get('settings_error') : ['reporting' => ['E_ALL'], 'display_error_details' => false, 'log_errors' => false, 'log_error_details' => false]); 56 | $this->reporting = (isset($this->settings['reporting']) ? $this->settings['reporting'] : ['E_ALL']); 57 | } 58 | 59 | public function __invoke() 60 | { 61 | $error = error_get_last(); 62 | if ($error) { 63 | $errorFile = $error['file']; 64 | $errorLine = $error['line']; 65 | $errorMessage = $error['message']; 66 | $errorType = $error['type']; 67 | $message = 'An error while processing your request. Please try again later.'; 68 | 69 | if ($this->displayErrorDetails) { 70 | switch ($errorType) { 71 | case E_USER_ERROR: 72 | if (!in_array('~E_ERROR', $this->reporting) && (in_array('E_ALL', $this->reporting) || in_array('E_ERROR', $this->reporting))) { 73 | $message = "FATAL ERROR: {$errorMessage}. "; 74 | $message .= " on line {$errorLine} in file {$errorFile}."; 75 | $this->logger->error($message); 76 | } 77 | break; 78 | 79 | case E_USER_WARNING: 80 | if (!in_array('~E_WARNING', $this->reporting) && (in_array('E_ALL', $this->reporting) || in_array('E_WARNING', $this->reporting))) { 81 | $message = "WARNING: {$errorMessage}"; 82 | $this->logger->warning($message); 83 | } 84 | break; 85 | 86 | case E_USER_NOTICE: 87 | if (!in_array('~E_NOTICE', $this->reporting) && (in_array('E_ALL', $this->reporting) || in_array('E_NOTICE', $this->reporting))) { 88 | $message = "NOTICE: {$errorMessage}"; 89 | $this->logger->notice($message); 90 | } 91 | break; 92 | 93 | default: 94 | if (in_array('E_ALL', $this->reporting)) { 95 | $message = "ERROR: {$errorMessage}"; 96 | $message .= " on line {$errorLine} in file {$errorFile}."; 97 | $this->logger->error($message); 98 | } 99 | break; 100 | } 101 | } 102 | 103 | $exception = new HttpInternalServerErrorException($this->request, $message); 104 | $response = $this->errorHandler->__invoke($this->request, $exception, $this->displayErrorDetails, false, false); 105 | 106 | if (ob_get_length()) { 107 | ob_clean(); 108 | } 109 | 110 | $responseEmitter = new ResponseEmitter(); 111 | $responseEmitter->emit($response); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /templates/default/js/editorjs/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BlockTool, BlockToolConstructorOptions, BlockToolData, PasteConfig, PasteEvent, SanitizerConfig, ToolboxConfig } from '@editorjs/editorjs'; 2 | 3 | /** 4 | * CodeTool for Editor.js 5 | * @version 2.0.0 6 | * @license MIT 7 | */ 8 | /** 9 | * Data structure for CodeTool's data 10 | */ 11 | export type CodeData = BlockToolData<{ 12 | /** 13 | * The code content input by the user 14 | */ 15 | code: string; 16 | }>; 17 | /** 18 | * Configuration options for the CodeTool provided by the user 19 | */ 20 | export interface CodeConfig { 21 | /** 22 | * Placeholder text to display in the input field when it's empty 23 | */ 24 | placeholder: string; 25 | } 26 | /** 27 | * Options passed to the constructor of a block tool 28 | */ 29 | export type CodeToolConstructorOptions = BlockToolConstructorOptions; 30 | /** 31 | * Code Tool for the Editor.js allows to include code examples in your articles. 32 | */ 33 | export default class CodeTool implements BlockTool { 34 | /** 35 | * API provided by Editor.js for interacting with the editor's core functionality 36 | */ 37 | private api; 38 | /** 39 | * Indicates whether the editor is in read-only mode, preventing modifications 40 | */ 41 | private readOnly; 42 | /** 43 | * Placeholder text displayed when there is no code content 44 | */ 45 | private placeholder; 46 | /** 47 | * Collection of CSS class names used by CodeTool for styling its elements 48 | */ 49 | private CSS; 50 | /** 51 | * DOM nodes related to the CodeTool, including containers and other elements 52 | */ 53 | private nodes; 54 | /** 55 | * Stores the current data (code and other related properties) for the CodeTool 56 | */ 57 | private _data; 58 | /** 59 | * Notify core that read-only mode is supported 60 | * @returns true if read-only mode is supported 61 | */ 62 | static get isReadOnlySupported(): boolean; 63 | /** 64 | * Allows pressing Enter key to create line breaks inside the CodeTool textarea 65 | * This enables multi-line input within the code editor. 66 | * @returns true if line breaks are allowed in the textarea 67 | */ 68 | static get enableLineBreaks(): boolean; 69 | /** 70 | * Render plugin`s main Element and fill it with saved data 71 | * @param options - tool constricting options 72 | * @param options.data — previously saved plugin code 73 | * @param options.config - user config for Tool 74 | * @param options.api - Editor.js API 75 | * @param options.readOnly - read only mode flag 76 | */ 77 | constructor({ data, config, api, readOnly }: CodeToolConstructorOptions); 78 | /** 79 | * Return Tool's view 80 | * @returns this.nodes.holder - Code's wrapper 81 | */ 82 | render(): HTMLDivElement; 83 | /** 84 | * Extract Tool's data from the view 85 | * @param codeWrapper - CodeTool's wrapper, containing textarea with code 86 | * @returns - saved plugin code 87 | */ 88 | save(codeWrapper: HTMLDivElement): CodeData; 89 | /** 90 | * onPaste callback fired from Editor`s core 91 | * @param event - event with pasted content 92 | */ 93 | onPaste(event: PasteEvent): void; 94 | /** 95 | * Returns Tool`s data from private property 96 | * @returns 97 | */ 98 | get data(): CodeData; 99 | /** 100 | * Set Tool`s data to private property and update view 101 | * @param data - saved tool data 102 | */ 103 | set data(data: CodeData); 104 | /** 105 | * Get Tool toolbox settings. 106 | * Provides the icon and title to display in the toolbox for the CodeTool. 107 | * @returns An object containing: 108 | * - icon: SVG representation of the Tool's icon 109 | * - title: Title to show in the toolbox 110 | */ 111 | static get toolbox(): ToolboxConfig; 112 | /** 113 | * Default placeholder for CodeTool's textarea 114 | * @returns 115 | */ 116 | static get DEFAULT_PLACEHOLDER(): string; 117 | /** 118 | * Used by Editor.js paste handling API. 119 | * Provides configuration to handle CODE tag. 120 | * @returns 121 | */ 122 | static get pasteConfig(): PasteConfig; 123 | /** 124 | * Automatic sanitize config 125 | * @returns 126 | */ 127 | static get sanitize(): SanitizerConfig; 128 | /** 129 | * Handles Tab key pressing (adds/removes indentations) 130 | * @param event - keydown 131 | */ 132 | private tabHandler; 133 | /** 134 | * Create Tool's view 135 | * @returns 136 | */ 137 | private drawView; 138 | } 139 | -------------------------------------------------------------------------------- /templates/admin/login.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login - PerSeo CMS Admin 7 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 81 | 82 | 83 | 84 | 85 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /templates/admin/users/edit.twig: -------------------------------------------------------------------------------- 1 | {% extends "admin/layout.twig" %} 2 | 3 | {% block title %}Edit User{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

Edit User

11 |
12 |
13 | 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 |
35 | 36 | 37 |
38 | 39 |
40 | 41 | 42 |
43 | 44 |
45 | 46 | 51 |
52 | 53 |
54 | 55 | 59 |
60 | 61 | 64 |
65 |
66 |
67 |
68 |
69 |
70 | {% endblock %} 71 | 72 | {% block scripts %} 73 | 119 | {% endblock %} 120 | -------------------------------------------------------------------------------- /templates/admin/permissions/create.twig: -------------------------------------------------------------------------------- 1 | {% extends "admin/layout.twig" %} 2 | 3 | {% block title %}Create Permission{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

Create Permission

11 |
12 |
13 | 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |

Permission Information

30 |
31 |
32 |
33 |
34 | 35 | 43 | 44 | Unique identifier (lowercase, use underscores) 45 | 46 |
47 | ${errors.slug[0]} 48 |
49 |
50 | 51 |
52 | 53 | 59 | 60 | Describe what this permission allows users to do 61 | 62 |
63 |
64 |
65 | 74 |
75 |
76 |
77 |
78 |
79 | {% endblock %} 80 | 81 | {% block scripts %} 82 | 124 | {% endblock %} 125 | -------------------------------------------------------------------------------- /modules/wizard/Classes/InstallSQLiteDB.php: -------------------------------------------------------------------------------- 1 | fileconf = $fileconf; 16 | $this->path = $path; 17 | } 18 | 19 | public function createDB(string $driver, string $dbfile, string $prefix = ''): string { 20 | try { 21 | $filedb = $this->path . DIRECTORY_SEPARATOR .'config'. DIRECTORY_SEPARATOR . $dbfile; 22 | $configfile = (file_exists($this->fileconf) ? $this->fileconf : ''); 23 | $content = file_get_contents($configfile); 24 | $dbSettings = << [ 27 | 'default' => [ 28 | 'type' => '$driver', 29 | 'database' => '$filedb', 30 | 'prefix' => '$prefix' 31 | ]\n\t 32 | PHP; 33 | if (preg_match_all('/\]\s*\];/s', $content, $matches, PREG_OFFSET_CAPTURE)) { 34 | $pos = end(end($matches[0])); 35 | $newContent = substr_replace($content, $dbSettings, $pos, 0); 36 | file_put_contents($configfile, $newContent); 37 | } else { 38 | throw new Exception("Error edit config file", 3); 39 | } 40 | $db = new DB([ 41 | 'type' => $driver, 42 | 'database' => $filedb, 43 | 'error' => \PDO::ERRMODE_EXCEPTION 44 | ]); 45 | $db->create($prefix ."admins", [ 46 | "id" => [ 47 | "INTEGER", 48 | "PRIMARY KEY" 49 | ], 50 | "user" => [ 51 | "VARCHAR(100)", 52 | "NOT NULL" 53 | ], 54 | "pass" => [ 55 | "VARCHAR(255)", 56 | "NOT NULL" 57 | ], 58 | "email" => [ 59 | "VARCHAR(255)", 60 | "NOT NULL" 61 | ], 62 | "superuser" => [ 63 | "VARCHAR(255)", 64 | "NULL" 65 | ], 66 | "type" => [ 67 | "INTEGER(2)", 68 | "NULL" 69 | ], 70 | "stato" => [ 71 | "INTEGER(2)", 72 | "NOT NULL", 73 | "DEFAULT", 74 | 1 75 | ] 76 | ]); 77 | $db->create($prefix ."cookies", [ 78 | "id" => [ 79 | "INTEGER", 80 | "PRIMARY KEY" 81 | ], 82 | "uid" => [ 83 | "INTEGER(100)", 84 | "NOT NULL" 85 | ], 86 | "uuid" => [ 87 | "VARCHAR(255)", 88 | "NOT NULL" 89 | ], 90 | "type" => [ 91 | "VARCHAR(10)", 92 | "NOT NULL" 93 | ], 94 | "auth_token" => [ 95 | "VARCHAR(255)", 96 | "NOT NULL" 97 | ], 98 | "lastseen" => [ 99 | "TIMESTAMP", 100 | "DEFAULT", 101 | "CURRENT_TIMESTAMP" 102 | ] 103 | ]); 104 | $db->create($prefix ."admins_types", [ 105 | "id" => [ 106 | "INTEGER", 107 | "PRIMARY KEY" 108 | ], 109 | "pid" => [ 110 | "INTEGER(100)", 111 | "NOT NULL" 112 | ], 113 | "label" => [ 114 | "VARCHAR(100)", 115 | "NOT NULL" 116 | ] 117 | ]); 118 | $db->create($prefix ."routes", [ 119 | "id" => [ 120 | "INTEGER", 121 | "PRIMARY KEY" 122 | ], 123 | "request" => [ 124 | "VARCHAR(255)", 125 | "NOT NULL" 126 | ], 127 | "dest" => [ 128 | "VARCHAR(255)", 129 | "NOT NULL" 130 | ], 131 | "type" => [ 132 | "INTEGER(2)", 133 | "NOT NULL", 134 | "DEFAULT", 135 | 1 136 | ], 137 | "redirect" => [ 138 | "INTEGER(3)", 139 | "NOT NULL", 140 | "DEFAULT", 141 | 301 142 | ], 143 | "canonical" => [ 144 | "INTEGER(2)", 145 | "NOT NULL", 146 | "DEFAULT", 147 | 0 148 | ] 149 | ]); 150 | unset($db); 151 | $fixht = $this->fixhtaccess($dbfile); 152 | $result = array( 153 | 'code' => '0', 154 | 'msg' => 'OK' 155 | ); 156 | } catch (PDOException $e) { 157 | $result = array( 158 | 'code' => $e->getCode(), 159 | 'msg' => $e->getMessage() 160 | ); 161 | } 162 | catch (Exception $e) { 163 | $result = array( 164 | 'code' => $e->getCode(), 165 | 'msg' => $e->getMessage() 166 | ); 167 | } 168 | return json_encode($result); 169 | } 170 | protected function fixhtaccess($dbfile): string { 171 | try { 172 | $htaccessPath = $this->path . DIRECTORY_SEPARATOR .'.htaccess'; 173 | $relativePath = "$dbfile"; 174 | $insertBlock = << 177 | Require all denied 178 | 179 | HTACCESS; 180 | $contents = file_get_contents($htaccessPath); 181 | if (strpos($contents, '') === false) { 182 | $pattern = '/^(DirectoryIndex\s+index\.php\s*)$/mi'; 183 | $modified = preg_replace($pattern, "$1" . $insertBlock, $contents, 1); 184 | file_put_contents($htaccessPath, $modified); 185 | } 186 | $result = array( 187 | 'code' => '0', 188 | 'msg' => 'OK' 189 | ); 190 | } 191 | catch (Exception $e) { 192 | $result = array( 193 | 'code' => $e->getCode(), 194 | 'msg' => $e->getMessage() 195 | ); 196 | } 197 | return json_encode($result); 198 | } 199 | } -------------------------------------------------------------------------------- /core/perseo/Handlers/HttpErrorHandler.php: -------------------------------------------------------------------------------- 1 | exception; 33 | $statusCode = 500; 34 | $type = self::SERVER_ERROR; 35 | $description = 'An internal error has occurred while processing your request.'; 36 | 37 | if ($exception instanceof HttpException) { 38 | $statusCode = $exception->getCode(); 39 | $description = $exception->getMessage(); 40 | 41 | if ($exception instanceof HttpNotFoundException) { 42 | $statusCode = 404; 43 | $type = self::RESOURCE_NOT_FOUND; 44 | } elseif ($exception instanceof HttpMethodNotAllowedException) { 45 | $statusCode = 405; 46 | $type = self::NOT_ALLOWED; 47 | } elseif ($exception instanceof HttpUnauthorizedException) { 48 | $statusCode = 401; 49 | $type = self::UNAUTHENTICATED; 50 | } elseif ($exception instanceof HttpForbiddenException) { 51 | $statusCode = 403; 52 | $type = self::UNAUTHENTICATED; 53 | } elseif ($exception instanceof HttpBadRequestException) { 54 | $statusCode = 400; 55 | $type = self::BAD_REQUEST; 56 | } elseif ($exception instanceof HttpNotImplementedException) { 57 | $statusCode = 501; 58 | $type = self::NOT_IMPLEMENTED; 59 | } 60 | } elseif ($exception instanceof PDOException) { 61 | // Handle PDOException specifically 62 | $statusCode = 500; 63 | $type = self::DATABASE_ERROR; 64 | $description = 'A database error occurred.'; 65 | 66 | if ($this->displayErrorDetails) { 67 | $description = $exception->getMessage(); 68 | } 69 | 70 | // Log PDO exceptions with details 71 | if ($this->logger) { 72 | $this->logger->error('PDOException: ' . $exception->getMessage(), [ 73 | 'code' => $exception->getCode(), 74 | 'file' => $exception->getFile(), 75 | 'line' => $exception->getLine() 76 | ]); 77 | } 78 | } elseif ($exception instanceof Throwable) { 79 | // Handle all other exceptions 80 | $statusCode = 500; 81 | $type = self::SERVER_ERROR; 82 | $description = 'An internal server error occurred.'; 83 | 84 | if ($this->displayErrorDetails) { 85 | $description = $exception->getMessage(); 86 | } 87 | 88 | // Log all exceptions 89 | if ($this->logger) { 90 | $this->logger->error('Exception: ' . $exception->getMessage(), [ 91 | 'code' => $exception->getCode(), 92 | 'file' => $exception->getFile(), 93 | 'line' => $exception->getLine(), 94 | 'trace' => $exception->getTraceAsString() 95 | ]); 96 | } 97 | } 98 | 99 | $error = [ 100 | 'statusCode' => $statusCode, 101 | 'error' => [ 102 | 'type' => $type, 103 | 'description' => $description, 104 | ], 105 | ]; 106 | 107 | // Add debug information if displayErrorDetails is enabled 108 | if ($this->displayErrorDetails && $exception instanceof Throwable) { 109 | $error['debug'] = [ 110 | 'message' => $exception->getMessage(), 111 | 'file' => $exception->getFile(), 112 | 'line' => $exception->getLine(), 113 | 'trace' => $exception->getTrace() 114 | ]; 115 | } 116 | 117 | $encodedPayload = json_encode($error, JSON_PRETTY_PRINT); 118 | 119 | $response = $this->responseFactory->createResponse($statusCode); 120 | $response->getBody()->write($encodedPayload); 121 | 122 | return $response->withHeader('Content-Type', 'application/json'); 123 | } 124 | } -------------------------------------------------------------------------------- /templates/admin/permissions/edit.twig: -------------------------------------------------------------------------------- 1 | {% extends "admin/layout.twig" %} 2 | 3 | {% block title %}Edit Permission{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

Edit Permission

11 |
12 |
13 | 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |

Permission Information

30 |
31 |
32 |
33 |
34 | 35 | 43 | 44 | Unique identifier (lowercase, use underscores) 45 | 46 |
47 | ${errors.slug[0]} 48 |
49 |
50 | 51 |
52 | 53 | 59 | 60 | Describe what this permission allows users to do 61 | 62 |
63 |
64 |
65 | 74 |
75 |
76 |
77 |
78 |
79 | {% endblock %} 80 | 81 | {% block scripts %} 82 | 125 | {% endblock %} 126 | -------------------------------------------------------------------------------- /core/perseo/MiddleWare/Alias.php: -------------------------------------------------------------------------------- 1 | app = $app; 22 | $this->db = ($container->has('db') ? $container->get('db') : null); 23 | $this->settings = ($container->has('settings_db') ? $container->get('settings_db')['default'] : ['type' => 'nodb']); 24 | } 25 | 26 | public function process(Request $request, RequestHandler $handler): Response 27 | { 28 | if (!empty($this->db) && is_object($this->db)) { 29 | $fulluri = (string) $request->getUri()->getPath(); 30 | $basepath = (string) $this->app->getBasePath(); 31 | $uri = (string) substr($fulluri, strlen($basepath)); 32 | $filteredreq = preg_replace('/[^a-zA-Z0-9-_.\-\/]/', '#', $uri); 33 | $notonlynumeric = preg_replace('/[^a-zA-Z0-9]/', '', $filteredreq); 34 | $regmatch = (((substr($filteredreq, -1) == '/') && (strlen($filteredreq) > 1)) ? substr_replace($filteredreq, '', -1) : $filteredreq) . '([/]?)$'; 35 | if (!is_numeric($notonlynumeric)) { 36 | if($this->settings['type'] == 'mysql') { 37 | $result = $this->db->select('routes', [ 38 | 'request', //URI Requested 39 | 'dest', //Destination for redirect or alias 40 | 'type', //If is Alias or is a Redirect 41 | 'redirect', //HTTP Redirect Code (301, 302) 42 | 'canonical', //If route is canonical (For SEO) 43 | 'priority' => DB::RAW('IF(REGEXP_REPLACE(request, :regmatch, 1) = 1, 1, 2)', [ 44 | ":regmatch" => $regmatch 45 | ]) //Request match has always priority to the destination, to avoid mismatches. 46 | ], [ 47 | "OR" => [ 48 | "AND #alias" => [ 49 | 'request[REGEXP]' => $regmatch 50 | ], 51 | "AND #redirect" => [ 52 | 'dest[REGEXP]' => $regmatch 53 | ] 54 | ], 55 | "ORDER" => 'priority' 56 | ]); 57 | } 58 | elseif($this->settings['type'] == 'pgsql') { 59 | $tableName = $this->settings['prefix'] .'routes'; 60 | $sql = "SELECT 61 | request, 62 | dest, 63 | type, 64 | redirect, 65 | canonical, 66 | CASE WHEN request ~ :regmatch THEN 1 ELSE 2 END AS priority 67 | FROM \"$tableName\" 68 | WHERE request ~ :regmatch OR dest ~ :regmatch 69 | ORDER BY priority"; 70 | $stmt = $this->db->pdo->prepare($sql); 71 | $stmt->execute([':regmatch' => $regmatch]); 72 | 73 | $result = $stmt->fetchAll(\PDO::FETCH_ASSOC); 74 | } 75 | else if($this->settings['type'] == 'sqlite') { 76 | $result = $this->db->select('routes', [ 77 | 'request', //URI Requested 78 | 'dest', //Destination for redirect or alias 79 | 'type', //If is Alias or is a Redirect 80 | 'redirect', //HTTP Redirect Code (301, 302) 81 | 'canonical', //If route is canonical (For SEO) 82 | 'priority' => DB::RAW('CASE WHEN request REGEXP :regmatch THEN 1 ELSE 2 END', [ 83 | ":regmatch" => $regmatch 84 | ]) //Request match has always priority to the destination, to avoid mismatches. 85 | ], [ 86 | "OR" => [ 87 | "AND #alias" => [ 88 | 'request[REGEXP]' => $regmatch 89 | ], 90 | "AND #redirect" => [ 91 | 'dest[REGEXP]' => $regmatch 92 | ] 93 | ], 94 | "ORDER" => 'priority' 95 | ]); 96 | } 97 | if (!empty($result)) { 98 | $req = (string) $result[0]['request']; 99 | $dest = (string) $result[0]['dest']; 100 | $type = (string) $result[0]['type']; 101 | $redirect = (int) $result[0]['redirect']; 102 | $basepath = (string) $this->app->getBasePath(); 103 | $mydest = (string) $basepath . $dest; 104 | if ($type == 1) { 105 | if ($uri != $req) { 106 | $mydest = (string) $basepath . $req; 107 | $response = $this->app->getResponseFactory()->createResponse(); 108 | return $response->withHeader('Location', $mydest)->withStatus($redirect); 109 | } else { 110 | $request = $request->withUri($request->getUri()->withPath($mydest)); 111 | } 112 | } elseif ($type == 2) { 113 | if ($uri != $dest) { 114 | $response = $this->app->getResponseFactory()->createResponse(); 115 | return $response->withHeader('Location', $mydest)->withStatus($redirect); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | $response = $handler->handle($request); 122 | return $response; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /modules/admin/Middleware/AuthMiddleware.php: -------------------------------------------------------------------------------- 1 | app = $app; 26 | $this->container = $container; 27 | $this->session = $container->get(\Odan\Session\SessionInterface::class); 28 | $this->settings = $container->get('settings_global'); 29 | $this->db = $container->get('db'); 30 | $this->settingcookie = $container->get('settings_cookie'); 31 | } 32 | 33 | public function process(Request $request, RequestHandler $handler): Response 34 | { 35 | $uri = $request->getUri()->getPath(); 36 | $basepath = (string) $this->app->getBasePath(); 37 | if ($this->settings['locale']) { 38 | $basepath = $basepath .'/'. $request->getAttribute('locale'); 39 | } 40 | // Remove basepath from URI 41 | $path = substr($uri, strlen($basepath)); 42 | 43 | // Allow login page and auth endpoints 44 | if (strpos($path, '/'. $this->settings['adminpath'] .'/login') === 0 || strpos($path, '/'. $this->settings['adminpath'] .'/auth') === 0) { 45 | return $handler->handle($request); 46 | } 47 | 48 | // Check if session is not set but JWT cookie exists - restore session from JWT 49 | if (!$this->session->has('admin.login') || !$this->session->get('admin.login')) { 50 | // Try to restore session from JWT 51 | $settings = $this->container->has('settings_secure') ? $this->container->get('settings_secure') : []; 52 | $jwtHelper = new JWTHelper($settings); 53 | $token = $jwtHelper->extractTokenFromRequest($request, $this->settingcookie['admin']); 54 | 55 | if ($token) { 56 | $userData = $jwtHelper->getUserFromToken($token); 57 | 58 | if ($userData && !empty($userData['ulid'])) { 59 | // JWT is valid, restore session from database using ulid 60 | $adminData = $this->db->query("SELECT as id, as ulid, as user, as pass, as email, CONCAT('[',perm.perms,']') as permissions FROM 61 | INNER JOIN (SELECT as role_id, GROUP_CONCAT(JSON_OBJECT('id', , 'slug', ) ORDER BY ASC) AS perms FROM 62 | INNER JOIN ON = 63 | INNER JOIN ON = GROUP BY ) perm ON = perm.role_id WHERE = 1 AND = :ulid", [ 64 | ':ulid' => $userData['ulid'] 65 | ])->fetchAll(\PDO::FETCH_ASSOC); 66 | 67 | if (!empty($adminData)) { 68 | $this->session->set('admin.login', true); 69 | $this->session->set('admin.id', (int) $adminData[0]['id']); 70 | $this->session->set('admin.ulid', (string) $adminData[0]['ulid']); 71 | $this->session->set('admin.user', (string) $adminData[0]['user']); 72 | $this->session->set('admin.permissions', (string) ($adminData[0]['permissions'] ?? '[]')); 73 | } 74 | } 75 | } 76 | } 77 | 78 | // Check if user is authenticated using MLogin session vars 79 | // MLogin sets: admin.login, admin.id, admin.ulid, admin.user, admin.permissions 80 | if (!$this->session->has('admin.login') || $this->session->get('admin.login') !== true) { 81 | $response = new SlimResponse(); 82 | 83 | // For AJAX requests, return JSON 84 | if ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest' 85 | || $request->getHeaderLine('Accept') === 'application/json') { 86 | $response->getBody()->write(json_encode([ 87 | 'success' => false, 88 | 'message' => 'Unauthorized', 89 | 'redirect' => $basepath . '/'. $this->settings['adminpath'] .'/login' 90 | ])); 91 | return $response->withHeader('Content-Type', 'application/json')->withStatus(401); 92 | } 93 | 94 | // For regular requests, redirect to login 95 | return $response 96 | ->withHeader('Location', $basepath . '/'. $this->settings['adminpath'] .'/login') 97 | ->withStatus(302); 98 | } 99 | 100 | return $handler->handle($request); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /modules/admin/routes.php: -------------------------------------------------------------------------------- 1 | get('/admin/login', [LoginViewController::class, 'showLogin']); 19 | $app->post('/admin/auth/login', CLogin::class); 20 | $app->get('/admin/auth/check', [LoginViewController::class, 'checkAuth']); 21 | 22 | // Protected routes (authentication required) 23 | $app->group('/admin', function (RouteCollectorProxy $group) { 24 | // Dashboard 25 | $group->get('[/]', [DashboardController::class, 'index']); 26 | $group->get('/dashboard', [DashboardController::class, 'index']); 27 | 28 | // Auth 29 | $group->get('/logout', CLogout::class); 30 | 31 | // Users 32 | $group->get('/users', [UserController::class, 'index']); 33 | $group->get('/users/create', [UserController::class, 'create']); 34 | $group->post('/users', [UserController::class, 'store']); 35 | $group->get('/users/edit/{id}', [UserController::class, 'edit']); 36 | $group->post('/users/save/{id}', [UserController::class, 'update']); 37 | $group->delete('/users/{id}', [UserController::class, 'delete']); 38 | 39 | // API endpoints for users 40 | $group->get('/api/users', [UserController::class, 'getAll']); 41 | $group->get('/api/users/{id}', [UserController::class, 'getOne']); 42 | 43 | // Pages 44 | $group->get('/pages', [PageController::class, 'index']); 45 | $group->get('/pages/new', [PageController::class, 'create']); 46 | $group->get('/pages/create', [PageController::class, 'create']); // Backward compatibility 47 | $group->post('/pages', [PageController::class, 'store']); 48 | $group->post('/pages/save/{id}', [PageController::class, 'update']); 49 | $group->get('/pages/edit/{id}', [PageController::class, 'edit']); 50 | $group->get('/pages/{id}', [PageController::class, 'edit']); // Backward compatibility 51 | $group->put('/pages/{id}', [PageController::class, 'update']); // Backward compatibility 52 | $group->delete('/pages/delete/{id}', [PageController::class, 'delete']); 53 | $group->delete('/pages/{id}', [PageController::class, 'delete']); // Backward compatibility 54 | 55 | // API endpoints for pages 56 | $group->get('/api/pages', [PageController::class, 'getAll']); 57 | $group->get('/api/pages/{id}', [PageController::class, 'getOne']); 58 | 59 | // Posts 60 | $group->get('/posts', [PostController::class, 'index']); 61 | $group->get('/posts/create', [PostController::class, 'create']); 62 | $group->post('/posts', [PostController::class, 'store']); 63 | $group->get('/posts/edit/{id}', [PostController::class, 'edit']); 64 | $group->post('/posts/save/{id}', [PostController::class, 'update']); 65 | $group->put('/posts/{id}', [PostController::class, 'update']); // Backward compatibility 66 | $group->delete('/posts/{id}', [PostController::class, 'delete']); 67 | 68 | // API endpoints for posts 69 | $group->get('/api/posts', [PostController::class, 'getAll']); 70 | $group->get('/api/posts/{id}', [PostController::class, 'getOne']); 71 | 72 | // Media 73 | $group->get('/media', [MediaController::class, 'index']); 74 | $group->post('/media/upload', [MediaController::class, 'upload']); 75 | $group->delete('/media/{id}', [MediaController::class, 'delete']); 76 | 77 | // API endpoints for media 78 | $group->get('/api/media', [MediaController::class, 'getAll']); 79 | $group->get('/api/media/{id}', [MediaController::class, 'getOne']); 80 | 81 | // Roles 82 | $group->get('/roles', [RoleController::class, 'index']); 83 | $group->get('/roles/create', [RoleController::class, 'create']); 84 | $group->post('/roles', [RoleController::class, 'store']); 85 | $group->get('/roles/edit/{id}', [RoleController::class, 'edit']); 86 | $group->put('/roles/{id}', [RoleController::class, 'update']); 87 | $group->delete('/roles/{id}', [RoleController::class, 'delete']); 88 | 89 | // API endpoints for roles 90 | $group->get('/api/roles', [RoleController::class, 'getAll']); 91 | $group->get('/api/roles/{id}', [RoleController::class, 'getOne']); 92 | 93 | // Permissions 94 | $group->get('/permissions', [PermissionController::class, 'index']); 95 | $group->get('/permissions/create', [PermissionController::class, 'create']); 96 | $group->post('/permissions', [PermissionController::class, 'store']); 97 | $group->get('/permissions/{id}', [PermissionController::class, 'edit']); 98 | $group->put('/permissions/{id}', [PermissionController::class, 'update']); 99 | $group->delete('/permissions/{id}', [PermissionController::class, 'delete']); 100 | 101 | // API endpoints for permissions 102 | $group->get('/api/permissions', [PermissionController::class, 'getAll']); 103 | $group->get('/api/permissions/{id}', [PermissionController::class, 'getOne']); 104 | 105 | })->add(AuthMiddleware::class); -------------------------------------------------------------------------------- /templates/admin/users/create.twig: -------------------------------------------------------------------------------- 1 | {% extends "admin/layout.twig" %} 2 | 3 | {% block title %}Create User{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

Create New User

11 |
12 |
13 | 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 |
35 | 36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 | 44 |
45 |
46 |
47 |
48 | 49 | 50 |
51 |
52 |
53 | 54 |
55 | 56 | 57 |
58 | 59 |
60 | 61 | 66 |
67 | 68 |
69 | 70 | 75 |
76 | 77 | 80 |
81 |
82 |
83 |
84 |
85 |
86 | {% endblock %} 87 | 88 | {% block scripts %} 89 | 136 | {% endblock %} 137 | --------------------------------------------------------------------------------