diff --git a/.gitignore b/.gitignore index c59d41c..3687314 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,11 @@ -cache/structure.txt -cache/sitemap.xml -cache/lastSitemap.txt -cache/stats.txt +cache settings/settings.yaml settings/users plugins/admin plugins/demo plugins/disqus +plugins/download plugins/finalwords -plugins/share plugins/version system/vendor tests diff --git a/cache/lastCache.txt b/cache/lastCache.txt index 3603ce6..059d42c 100644 --- a/cache/lastCache.txt +++ b/cache/lastCache.txt @@ -1 +1 @@ -1526030510 \ No newline at end of file +1527021720 \ No newline at end of file diff --git a/content/1_getting-started/02-settings.md b/content/1_getting-started/02-settings.md index ee460aa..9f6bc2f 100644 --- a/content/1_getting-started/02-settings.md +++ b/content/1_getting-started/02-settings.md @@ -1,6 +1,6 @@ # Settings -As of Version 1.1.3 you can edit all settings in the new authoring panel of TYPEMILL. Just login to your typemill installation and go to settings. There you can edit: +As of Version 1.1.3 you can edit all settings in the new authoring panel of TYPEMILL. Just visit the url `yourwebsite.com/tm/login` and go to settings after the login. There you can edit: * The system (basic settings). * Themes (choose themes and configure it). @@ -17,4 +17,5 @@ There are some settings that are not available via the author panel. Most of the displayErrorDetails: true ```` -Don't forget to set it back to `false` before you deploy the website live. It is not secure to show the world your internal errors and many hosters will turn off all public error reports by default. \ No newline at end of file +Don't forget to set it back to `false` before you deploy the website live. It is not secure to show the world your internal errors and many hosters will turn off all public error reports by default. + diff --git a/content/2_for-writers/03-author-panel.md b/content/2_for-writers/03-author-panel.md index d86d650..9aab467 100644 --- a/content/2_for-writers/03-author-panel.md +++ b/content/2_for-writers/03-author-panel.md @@ -3,7 +3,7 @@ After you have setup your TYPEMILL website, you can login to the author panel. The login url is ```` -https://yourwebsite.net/tm-author/login +https://yourwebsite.net/tm/login ```` You can also use the url `https://yourwebsite.net/setup` that redirects to the login screen. @@ -15,4 +15,5 @@ In the **settings area** of the author panel you can: * Activate and configure **plugins**. * Manage **users**. -the **content area** of the author-panel is not ready yet, but it is on it's way and will be published in near future. Then you can create and manage all the content of your website online. For time being you have to create your content offline e.g. with a markdown editor and upload your content files with a FTP software. \ No newline at end of file +the **content area** of the author-panel is not ready yet, but it is on it's way and will be published in near future. Then you can create and manage all the content of your website online. For time being you have to create your content offline e.g. with a markdown editor and upload your content files with a FTP software. + diff --git a/content/2_for-writers/60-lost-password.md b/content/2_for-writers/60-lost-password.md new file mode 100644 index 0000000..2b66e01 --- /dev/null +++ b/content/2_for-writers/60-lost-password.md @@ -0,0 +1,21 @@ +# Lost Your Password? + +TYPEMILL does not provide a password recovery, but there are two ways to create a new password. + +## Is there another admin? + +If there is another user with admin rights, then contact him. He can delete your user, create a new one and tell you the new password. Change the password immediately after login. + +## No other admin? + +If you are the only admin user, then please follow these steps: + +* Connect to your website (e.g. via FTP). +* Go to the folder `/settings` and backup the file `settings.yaml`. +* Then delete the file `settings.yaml` on your server. +* Go to `yoursite.com/setup`. +* Fill out the form. This will create a new admin user and a fresh settings-file. +* Upload your old settings-file, so your old settings are active again. +* If not done before e.g. via FTP, delete the old admin-user in the user management now. + +It might look a bit uncomfortable but it makes sure, that you are the owner of the website. \ No newline at end of file diff --git a/content/3_for-theme-developers/03-theme-meta.md b/content/3_for-theme-developers/03-theme-meta.md index c29c1a0..229e0f9 100644 --- a/content/3_for-theme-developers/03-theme-meta.md +++ b/content/3_for-theme-developers/03-theme-meta.md @@ -66,4 +66,35 @@ forms: TYPEMILL will use these definitions and generate input fields for the author panel on the fly, so that the user can edit the values and customize the theme. If you have defined settings with the same name as the field name (e.g. `chapter`), then the input field in the author panel will automatically be prefilled with your settings from the YAML-file. -If you read the YAML-definition for input fields carefully, then you will notice that the definitions are pretty similar HTML: You simply define types and attributes like input-type, labels and placeholders. Nearly all valid field-types and field attributes are supported. You can find a detailed list in the [documentation for plugins](/plugin-developers/documentation/field-overview). \ No newline at end of file +If you have a lot of fields, you can even group some fields together in a fieldset like this: + +```` +forms: + fields: + + chapter: + type: text + label: chapter + placeholder: Add Name for Chapter + required: true + + MyFirstfieldset: + type: fieldset + legend: Last Modified + fields: + + modified: + type: checkbox + label: Activate Last Modified + description: Show last modified date at the end of each page? + + modifiedText: + type: text + label: Last Modified Text + placeholder: Last Updated + +```` + +The fields `modified` and `modifiedText` will then be grouped in a fieldset with the legend `Last Modified`. + +If you read the YAML-definition for input fields carefully, then you will notice that the definitions are pretty similar to HTML: You simply define types and attributes like input-type, labels and placeholders. Nearly all valid field-types and field attributes are supported. You can find a detailed list in the [documentation for plugins](/plugin-developers/documentation/field-overview). \ No newline at end of file diff --git a/content/4_for-plugin-developers/02-documentation/06-field-overview.md b/content/4_for-plugin-developers/02-documentation/06-field-overview.md index 81c61a7..af235f7 100644 --- a/content/4_for-plugin-developers/02-documentation/06-field-overview.md +++ b/content/4_for-plugin-developers/02-documentation/06-field-overview.md @@ -47,6 +47,7 @@ TYPEMILL accepts the following field type definitions: * textarea * tel * url +* fieldset A simple field definition looks like this: @@ -174,6 +175,38 @@ SimpleCheckbox: description: Please check me ```` +## Using Fieldsets + +If you have a lot of fields, you can group them togeter with a fieldset like this: + +```` +forms: + fields: + + chapter: + type: text + label: chapter + placeholder: Add Name for Chapter + required: true + + MyFirstfieldset: + type: fieldset + legend: Last Modified + fields: + + modified: + type: checkbox + label: Activate Last Modified + description: Show last modified date at the end of each page? + + modifiedText: + type: text + label: Last Modified Text + placeholder: Last Updated +```` + +The fields `modified` and `modifiedText` will then be grouped in a fieldset with the legend `Last Modified`. + ## Example for a complete yaml configuration To sum it up, this is a complete example of a yaml configuration file for a plugin with the meta-description, a default value and a field definition for user input: @@ -201,3 +234,4 @@ forms: block: Block classic: Classic ```` + diff --git a/system/Controllers/AuthController.php b/system/Controllers/AuthController.php index f07bbaa..1c332fb 100644 --- a/system/Controllers/AuthController.php +++ b/system/Controllers/AuthController.php @@ -7,6 +7,7 @@ use Slim\Http\Request; use Slim\Http\Response; use Typemill\Models\Validation; use Typemill\Models\User; +use Typemill\Models\WriteYaml; class AuthController extends Controller { @@ -34,9 +35,37 @@ class AuthController extends Controller public function show(Request $request, Response $response, $args) { - $this->c->view->render($response, '/auth/login.twig'); - } + $data = array(); + /* check previous login attemps */ + $yaml = new WriteYaml(); + $logins = $yaml->getYaml('settings/users', '.logins'); + $userIP = $this->getUserIP(); + $userLogins = isset($logins[$userIP]) ? count($logins[$userIP]) : false; + + if($userLogins) + { + /* get the latest */ + $lastLogin = intval($logins[$userIP][$userLogins-1]); + + /* if last login is longer than 60 seconds ago, clear it. */ + if(time() - $lastLogin > 60) + { + unset($logins[$userIP]); + $yaml->updateYaml('settings/users', '.logins', $logins); + } + + /* Did the user made three login attemps that failed? */ + elseif($userLogins >= 3) + { + $timeleft = 60 - (time() - $lastLogin); + $data['messages'] = array('time' => $timeleft, 'error' => array( 'Too many bad logins. Please wait.')); + } + } + + $this->c->view->render($response, '/auth/login.twig', $data); + } + /** * signin an existing user * @@ -47,6 +76,36 @@ class AuthController extends Controller public function login(Request $request, Response $response) { + /* log user attemps to authenticate */ + $yaml = new WriteYaml(); + $logins = $yaml->getYaml('settings/users', '.logins'); + $userIP = $this->getUserIP(); + $userLogins = isset($logins[$userIP]) ? count($logins[$userIP]) : false; + + /* if there have been user logins before. You have to do this again, because user does not always refresh the login page and old login attemps are stored. */ + if($userLogins) + { + /* get the latest */ + $lastLogin = intval($logins[$userIP][$userLogins-1]); + + /* if last login is longer than 60 seconds ago, clear it and add this attempt */ + if(time() - $lastLogin > 60) + { + unset($logins[$userIP]); + $yaml->updateYaml('settings/users', '.logins', $logins); + } + + /* Did the user made three login attemps that failed? */ + elseif($userLogins >= 2) + { + $logins[$userIP][] = time(); + $yaml->updateYaml('settings/users', '.logins', $logins); + + return $response->withRedirect($this->c->router->pathFor('auth.show')); + } + } + + /* authentication */ $params = $request->getParams(); $validation = new Validation(); @@ -58,14 +117,26 @@ class AuthController extends Controller if($userdata && password_verify($params['password'], $userdata['password'])) { $user->login($userdata['username']); + + /* clear the user login attemps */ + if($userLogins) + { + unset($logins[$userIP]); + $yaml->updateYaml('settings/users', '.logins', $logins); + } + return $response->withRedirect($this->c->router->pathFor('settings.show')); } } + + /* if authentication failed, add attempt to log file */ + $logins[$userIP][] = time(); + $yaml->updateYaml('settings/users', '.logins', $logins); - $this->c->flash->addMessage('error', 'Ups, credentials were wrong, please try again.'); + $this->c->flash->addMessage('error', 'Ups, wrong password or username, please try again.'); return $response->withRedirect($this->c->router->pathFor('auth.show')); } - + /** * log out a user * @@ -82,5 +153,27 @@ class AuthController extends Controller } return $response->withRedirect($this->c->router->pathFor('auth.show')); - } + } + + private function getUserIP() + { + $client = @$_SERVER['HTTP_CLIENT_IP']; + $forward = @$_SERVER['HTTP_X_FORWARDED_FOR']; + $remote = $_SERVER['REMOTE_ADDR']; + + if(filter_var($client, FILTER_VALIDATE_IP)) + { + $ip = $client; + } + elseif(filter_var($forward, FILTER_VALIDATE_IP)) + { + $ip = $forward; + } + else + { + $ip = $remote; + } + + return $ip; + } } \ No newline at end of file diff --git a/system/Controllers/Controller.php b/system/Controllers/Controller.php index fbc152f..dc901f6 100644 --- a/system/Controllers/Controller.php +++ b/system/Controllers/Controller.php @@ -20,6 +20,11 @@ abstract class Controller $data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData(); unset($_SESSION['old']); + + $response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff'); + $response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN'); + $response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block'); + return $this->c->view->render($response, $route, $data); } diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index a74a7f2..cb14db7 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -90,7 +90,7 @@ class SettingsController extends Controller if(isset($themeSettings['forms']['fields'])) { $fields = $this->getFields($userSettings, 'themes', $themeName, $themeSettings); - + /* overwrite original theme form definitions with enhanced form objects */ $themedata[$themeName]['forms']['fields'] = $fields; } @@ -177,57 +177,73 @@ class SettingsController extends Controller /* then iterate through the fields */ foreach($objectSettings['forms']['fields'] as $fieldName => $fieldConfigs) { - /* and create a new field object with the field name and the field configurations. */ - $field = new Field($fieldName, $fieldConfigs); - - /* you have to prefil the value for the field with default settings, user settings or old user-input from form */ - $userValue = false; - - /* first, add the default values from the original plugin or theme settings. Ignore checkboxes, otherwiese they might be always checked */ - if(isset($objectSettings['settings'][$fieldName])) + if($fieldConfigs['type'] == 'fieldset') { - $userValue = $objectSettings['settings'][$fieldName]; - } - - /* now overwrite them with the local stored user settings */ - if(isset($userSettings[$objectType][$objectName][$fieldName])) - { - $userValue = $userSettings[$objectType][$objectName][$fieldName]; - } - - /* overwrite it with old input in form, if exists */ - if(isset($_SESSION['old'][$objectName][$fieldName])) - { - $userValue = $_SESSION['old'][$objectName][$fieldName]; - } - - /* now we have set the uservalue for the field. Prepopulate the field object with it now */ - if($field->getType() == "textarea") - { - if($userValue) - { - $field->setContent($userValue); - } - } - elseif($field->getType() == "checkbox") - { - /* needs special treatment, because field does not exist in settings if unchecked by user */ - if(isset($userSettings[$objectType][$objectName][$fieldName])) - { - $field->setAttribute('checked', 'checked'); - } - else - { - $field->unsetAttribute('checked'); - } + /* Create an array for the subsettings of the fieldset with the same structure and data as the original settings array */ + $subSettings = $objectSettings; + $subSettings['forms'] = $fieldConfigs; + + $fieldset = array(); + $fieldset['type'] = 'fieldset'; + $fieldset['legend'] = $fieldConfigs['legend']; + $fieldset['fields'] = $this->getFields($userSettings, $objectType, $objectName, $subSettings); + $fields[] = $fieldset; } else { - $field->setAttributeValue('value', $userValue); - } + /* and create a new field object with the field name and the field configurations. */ + $field = new Field($fieldName, $fieldConfigs); + + /* you have to prefil the value for the field with default settings, user settings or old user-input from form */ + $userValue = false; - /* add the field to the field-List with the plugin-name as key */ - $fields[] = $field; + /* first, add the default values from the original plugin or theme settings. Ignore checkboxes, otherwiese they might be always checked */ + if(isset($objectSettings['settings'][$fieldName])) + { + $userValue = $objectSettings['settings'][$fieldName]; + } + + /* now overwrite them with the local stored user settings */ + if(isset($userSettings[$objectType][$objectName][$fieldName])) + { + $userValue = $userSettings[$objectType][$objectName][$fieldName]; + } + + /* overwrite it with old input in form, if exists */ + if(isset($_SESSION['old'][$objectName][$fieldName])) + { + $userValue = $_SESSION['old'][$objectName][$fieldName]; + } + + /* now we have set the uservalue for the field. Prepopulate the field object with it now */ + if($field->getType() == "textarea") + { + if($userValue) + { + $field->setContent($userValue); + } + } + elseif($field->getType() == "checkbox") + { + /* needs special treatment, because field does not exist in settings if unchecked by user */ + if(isset($userSettings[$objectType][$objectName][$fieldName])) + { + $field->setAttribute('checked', 'checked'); + } + else + { + $field->unsetAttribute('checked'); + } + } + else + { + $field->setAttributeValue('value', $userValue); + } + + /* add the field to the field-List with the plugin-name as key */ + $fields[] = $field; + + } } return $fields; @@ -331,13 +347,31 @@ class SettingsController extends Controller /* fetch the original settings from the folder (plugin or theme) to get the field definitions */ $originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName); - if($originalSettings) + if(isset($originalSettings['forms']['fields'])) { + /* flaten the multi-dimensional array with fieldsets to a one-dimensional array */ + $originalFields = array(); + foreach($originalSettings['forms']['fields'] as $fieldName => $fieldValue) + { + if(isset($fieldValue['fields'])) + { + foreach($fieldValue['fields'] as $subFieldName => $subFieldValue) + { + $originalFields[$subFieldName] = $subFieldValue; + } + } + else + { + $originalFields[$fieldName] = $fieldValue; + } + } + /* take the user input data and iterate over all fields and values */ foreach($userInput as $fieldName => $fieldValue) { /* get the corresponding field definition from original plugin settings */ - $fieldDefinition = isset($originalSettings['forms']['fields'][$fieldName]) ? $originalSettings['forms']['fields'][$fieldName] : false; + $fieldDefinition = isset($originalFields[$fieldName]) ? $originalFields[$fieldName] : false; + if($fieldDefinition) { /* validate user input for this field */ @@ -345,18 +379,23 @@ class SettingsController extends Controller } if(!$fieldDefinition && $fieldName != 'active') { - $_SESSION['errors'][$objectName][$fieldName] = 'This field is not defined!'; + $_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!'); } } } } - + /*********************** ** USER MANAGEMENT ** ***********************/ public function showUser($request, $response, $args) { + if($_SESSION['role'] == 'editor' && $_SESSION['user'] !== $args['username']) + { + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] )); + } + $validate = new Validation(); if($validate->username($args['username'])) @@ -428,12 +467,25 @@ class SettingsController extends Controller public function updateUser($request, $response, $args) { if($request->isPost()) - { + { $params = $request->getParams(); $user = new User(); $userroles = $user->getUserroles(); $validate = new Validation(); - + + /* non admins have different update rights */ + if($_SESSION['role'] !== 'administrator') + { + /* if an editor tries to update other userdata than its own */ + if($_SESSION['user'] !== $params['username']) + { + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] )); + } + + /* non admins cannot change his userrole */ + $params['userrole'] = $_SESSION['role']; + } + if($validate->existingUser($params, $userroles)) { $userdata = array('username' => $params['username'], 'email' => $params['email'], 'userrole' => $params['userrole']); @@ -442,14 +494,14 @@ class SettingsController extends Controller { $user->updateUser($userdata); $this->c->flash->addMessage('info', 'Saved all changes'); - return $response->withRedirect($this->c->router->pathFor('user.list')); + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']])); } elseif($validate->newPassword($params)) { $userdata['password'] = $params['newpassword']; $user->updateUser($userdata); $this->c->flash->addMessage('info', 'Saved all changes'); - return $response->withRedirect($this->c->router->pathFor('user.list')); + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']])); } } @@ -465,6 +517,16 @@ class SettingsController extends Controller $params = $request->getParams(); $validate = new Validation(); $user = new User(); + + /* non admins have different update rights */ + if($_SESSION['role'] !== 'administrator') + { + /* if an editor tries to delete other user than its own */ + if($_SESSION['user'] !== $params['username']) + { + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] )); + } + } if($validate->username($params['username'])) { diff --git a/system/Extensions/TwigUserExtension.php b/system/Extensions/TwigUserExtension.php new file mode 100644 index 0000000..18bdb46 --- /dev/null +++ b/system/Extensions/TwigUserExtension.php @@ -0,0 +1,34 @@ +router = $router; + } + + public function __invoke(Request $request, Response $response, $next) + { + if(!isset($_SESSION['login']) || !isset($_SESSION['role'])) + { + $response = $response->withRedirect($this->router->pathFor('auth.show')); + } + + if($_SESSION['role'] != 'administrator') + { + $response = $response->withRedirect($this->router->pathFor('content.show')); + } + + return $next($request, $response); + } +} \ No newline at end of file diff --git a/system/Middleware/RedirectIfUnauthenticated.php b/system/Middleware/RedirectIfUnauthenticated.php index 07b0c1d..23f2c33 100644 --- a/system/Middleware/RedirectIfUnauthenticated.php +++ b/system/Middleware/RedirectIfUnauthenticated.php @@ -21,7 +21,7 @@ class RedirectIfUnauthenticated { $response = $response->withRedirect($this->router->pathFor('auth.show')); } - + return $next($request, $response); } } \ No newline at end of file diff --git a/system/Models/User.php b/system/Models/User.php index 5cb413f..7a9b5e2 100644 --- a/system/Models/User.php +++ b/system/Models/User.php @@ -13,12 +13,14 @@ class User extends WriteYaml /* get all plugins folder */ $users = array_diff(scandir($userDir), array('..', '.')); - + $cleanUser = array(); foreach($users as $key => $user) { + if($user == '.logins'){ continue; } $cleanUser[] = str_replace('.yaml', '', $user); } + return $cleanUser; } @@ -75,8 +77,13 @@ class User extends WriteYaml public function login($username) { - $_SESSION['user'] = $username; - $_SESSION['login'] = true; + $user = $this->getUser($username); + if($user) + { + $_SESSION['user'] = $user['username']; + $_SESSION['role'] = $user['userrole']; + $_SESSION['login'] = true; + } } public function generatePassword($password) diff --git a/system/Routes/Web.php b/system/Routes/Web.php index c8122bb..1365489 100644 --- a/system/Routes/Web.php +++ b/system/Routes/Web.php @@ -7,6 +7,7 @@ use Typemill\Controllers\SettingsController; use Typemill\Controllers\ContentController; use Typemill\Middleware\RedirectIfUnauthenticated; use Typemill\Middleware\RedirectIfAuthenticated; +use Typemill\Middleware\RedirectIfNoAdmin; if($settings['settings']['setup']) { @@ -26,23 +27,26 @@ else $app->get('/setup/welcome', AuthController::class . ':redirect')->setName('setup.welcome'); } -$app->get('/tm-author', AuthController::class . ':redirect'); -$app->get('/tm-author/login', AuthController::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router'])); -$app->post('/tm-author/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'])); -$app->get('/tm-author/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm-author/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->post('/tm-author/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm-author/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->post('/tm-author/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm-author/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->post('/tm-author/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm-author/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->post('/tm-author/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->post('/tm-author/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->post('/tm-author/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm-author/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm-author/user', SettingsController::class . ':listUser')->setName('user.list')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm-author/content', ContentController::class . ':showContent')->setName('content.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); +$app->get('/tm', AuthController::class . ':redirect'); +$app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router'])); +$app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'])); +$app->get('/tm/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); +$app->get('/tm/content', ContentController::class . ':showContent')->setName('content.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); + +$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); + +$app->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); +$app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); +$app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); + +$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); foreach($routes as $pluginRoute) { diff --git a/system/Settings.php b/system/Settings.php index b7ffb5e..8f2c75c 100644 --- a/system/Settings.php +++ b/system/Settings.php @@ -41,7 +41,9 @@ class Settings 'userPath' => $rootPath . 'settings' . DIRECTORY_SEPARATOR . 'users', 'authorPath' => __DIR__ . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR, 'contentFolder' => 'content', - 'version' => '1.1.5', + 'cache' => true, + 'cachePath' => $rootPath . 'cache', + 'version' => '1.1.4', 'setup' => true, 'welcome' => true ]; diff --git a/system/author/auth/login.twig b/system/author/auth/login.twig index 05687ce..7e9febc 100644 --- a/system/author/auth/login.twig +++ b/system/author/auth/login.twig @@ -22,11 +22,18 @@ {{ errors.password | first }} {% endif %} - + + +
+ + {{ csrf_field() | raw }} + + {% if messages.time %} +
wait {{ messages.time }} sec
+
Forgot password?
+ {% endif %} +
- - {{ csrf_field() | raw }} - diff --git a/system/author/css/style.css b/system/author/css/style.css index c67266a..763cbc4 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -1,5 +1,5 @@ /********************** -* HELPERS * +* TRANSITION * **********************/ a, a:link, a:visited, a:focus, a:hover, a:active, button, .button, input, .control-group, .sidebar-menu, .menu-action, .button-arrow{ @@ -10,59 +10,6 @@ a, a:link, a:visited, a:focus, a:hover, a:active, button, .button, input, .contr transition: all 0.2s ease; } -/********************** -* COLORS * -**********************/ - -body, .cardHead header{ background: #f9f8f6; color: #444; } -.errors .cardHead header{ background: #e0474c; color: #fff; } -.onoffswitch-label { border: 1px solid #999; } -.onoffswitch-inner:before, -.onoffswitch-inner:after { color: #FFFFFF; } -.onoffswitch-inner:before { background: #70c1b3; color: #f9f8f6; } -.onoffswitch-inner:after { background: #f9f8f6; color: #999999; } -.onoffswitch-switch { border: 1px solid #999; background: #f9f8f6; } -input, select, textarea, -.large img{ border: 1px solid #ddd; background: #fff; } -section, .welcome{ background: #FFFFFF; box-shadow: 0 0 2px #ddd; } -select:focus, input:focus, -textarea:focus{ border: 1px solid #FFF; outline: none; box-shadow: 0 0 2px #000; } -button[type="button"].has-settings:hover{ background: #efece7; } -button[type="button"].active{ background: #70c1b3; color: #f9f8f6; border-top: 1px solid #70c1b3; } -button[type="button"].has-settings.active:hover{ background: #66b0a3; border-top: 1px solid #66b0a3; } -button[type="button"]{ background: #f9f8f6; color: #444; border-top: 1px solid #ddd; } -input:disabled{ background: #f9f8f6; } -input[type="submit"], -a.button,a.button:link, -a.button:visited{ border: 2px solid #e0474c; background: #e0474c; color: #f9f8f6; } -input[type="submit"]:hover, -a.button:focus,a.button:hover, -a.button:active{ border: 2px solid #cc4146; background: #cc4146; } -.error label{ color: #e0474c; } -.error input, .error select, -.error textarea{ border: 1px solid #e0474c; } -.error, .error small, .error p{ color: #e0474c; } -.cardInner{ border: 1px solid #ddd; background: #FFF; } -.color-box{ border: 1px solid #ddd; } -label .help, .label .help{ color: #FFF; background: #999; } -.help .tooltip { color: #fff; background-color: #999; } -.help .tooltip:after{ border-color: transparent transparent transparent #999; } - -.checkmark,.radiomark { border: 2px solid #ccc; background-color: #fff;} -.control-group:hover input ~ .checkmark, -.control-group:hover input ~ .radiomark { background-color: #ccc; } -.control-group input:checked ~ .checkmark, -.control-group input:checked ~ .radiomark { border: 2px solid #70c1b3; background-color: #70c1b3; } -.control-group .checkmark:after { border: solid #fff; } -.control-group .radiomark:after { background: #fff; } -.control-group input:disabled ~ .checkmark, -.control-group input:disabled ~ .radiomark { border: 2px solid #eee; background-color: #eee; } -.cardInfo a, .cardInfo a:link, -.cardInfo a:visited{ color: #70c1b3; text-decoration: none;} -.cardInfo a:focus,.cardInfo a:hover, -.cardInfo a:active{ color: #70c1b3; text-decoration: underline;} - - /******************** * COMMONS * ********************/ @@ -72,6 +19,8 @@ html,body{ margin:0; } body{ + background: #f9f8f6; + color: #444; font-size: 16px; font-family: Helvetica, Calibri, Arial, sans-serif; -webkit-font-smoothing: antialiased; @@ -83,35 +32,32 @@ header, nav, h1, h2, h3, h4, h5, h6{ h1{ font-size: 1.8em; margin: 1em 0 0.8em; font-weight: 700: } h2{ font-size: 1.6em; margin: 1em 0 0.8em; font-weight: 700; } h3{ font-size: 1em; margin: 0.6em 0 0.6em; text-transform: uppercase; font-weight: 300; } - -/******************** -* SETUP FORM * -********************/ - -.setupWrapper{ - max-width: 300px; - margin-left: auto; - margin-right: auto; - margin-top: 8%; +.main{ + margin: 0px auto; + box-sizing:border-box; + padding: 20px; +} +section{ + background: #FFFFFF; + box-shadow: 0 0 2px #ddd; + position: relative; + margin-bottom: 40px; + padding: 20px 20px 40px; + box-sizing: border-box; +} +article{ + width: 100%; +} +aside.sidebar{ + display: block; + width: 100%; background: #fff; - padding: 40px; - border-radius: 3px; - box-shadow: 0px 0px 10px rgba(0,0,0,0.2); -} -.setupWrapper label{ - font-weight: 700; -} -.setupWrapper input[type="submit"] -{ - border-radius: 0px; - margin-bottom: 0px; -} -.setupWrapper .formElement{ - margin: 0 0 20px 0; + margin-bottom: 10px; + box-sizing: border-box; } /******************** -* AUTHOR NAVI * +* NAVI * ********************/ .main-header{ @@ -163,7 +109,6 @@ h3{ font-size: 1em; margin: 0.6em 0 0.6em; text-transform: uppercase; font-weigh border-bottom: 3px solid transparent; } .navi-items a:hover, .navi-items a:focus, .navi-items a:active, .navi-items a.active{ - background: #f9f8f6; border-bottom: 3px solid #e0474c; } .navi-items i{ @@ -176,21 +121,10 @@ h3{ font-size: 1em; margin: 0.6em 0 0.6em; text-transform: uppercase; font-weigh color: #e0474c; } -.main{ - margin: 0px auto; - box-sizing:border-box; - padding: 20px; -} -article{ - width: 100%; -} -aside.sidebar{ - display: block; - width: 100%; - background: #fff; - margin-bottom: 10px; - box-sizing: border-box; -} +/************************ +* MENU * +************************/ + .sidebar-menu{ max-height: 40px; padding: 0px 20px; @@ -212,6 +146,83 @@ aside.sidebar{ font-weight: 300; cursor: pointer; } +ul.menu-list{ + list-style: none; + margin:5px 0 30px; + padding:0; +} +li.menu-item{ + padding: 0; + margin: 8px 0; +} +.menu-item a, .menu-item a:link, .menu-item a:visited{ + background: #f9f8f6; + width: 100%; + box-sizing: border-box; + display: inline-block; + text-decoration: none; + color: #444; + border: 1px solid #eee; + border-radius: 3px; + line-height: 2.3em; +} +.menu-item a:hover, .menu-item a:focus, .menu-item a:active, .menu-item a.active{ + color: #fff; + background: #e0474c; +} + +/******************** +* CONTENT * +********************/ + +header h1, header h2{ + display: inline-block; +} +header input[type="submit"]{ + margin-top: 1.5em; + margin-right: 10px; + float: right; + width: 150px; + box-sizing:border-box; + border: 0px; +} + +/******************** +* SETUP FORM * +********************/ + +.setupWrapper{ + max-width: 300px; + margin-left: auto; + margin-right: auto; + margin-top: 8%; + background: #fff; + padding: 40px; + border-radius: 3px; + box-shadow: 0px 0px 10px rgba(0,0,0,0.2); +} +.setupWrapper label{ + font-weight: 700; +} +.setupWrapper input[type="submit"] +{ + border-radius: 0px; + margin-bottom: 0px; +} +.setupWrapper input[type="submit"]:disabled{ + pointer: cursor; + cursor: default; + background: #f9f8f6; + border: 2px solid #f9f8f6; +} +.setupWrapper .formElement{ + margin: 0 0 20px 0; +} + + +/************************ +* OPEN-CLOSE BUTTON * +************************/ .button-arrow{ width: 0; @@ -235,41 +246,6 @@ aside.sidebar{ border-bottom: 5px solid #fff; } - -ul.menu-list{ - list-style: none; - margin:5px 0 30px; - padding:0; -} -li.menu-item{ - padding: 0; - margin: 8px 0; -} -.menu-item a, .menu-item a:link, .menu-item a:visited{ - background: #f9f8f6; - width: 100%; - box-sizing: border-box; - display: inline-block; - text-decoration: none; - color: #444; - border: 1px solid #eee; - border-radius: 3px; - line-height: 2.3em; - -// padding: 0px 10px; -// line-height: 2px; -// width: auto; -// height: 0; -// border-top: 8px solid transparent; -// border-bottom: 8px solid transparent; -// border-left: 10px solid white; -} -.menu-item a:hover, .menu-item a:focus, .menu-item a:active, .menu-item a.active{ - color: #fff; - background: #e0474c; -// border-left: 10px solid #e0474c; -} - /************* ** MODAL ** *************/ @@ -330,6 +306,11 @@ a.openModal:link,a.openModal:active,a.openModal:visited{ a.openModal:focus,a.openModal:hover{ text-decoration: underline; } + +/***************** +** USER-LIST ** +*****************/ + ul.userlist{ padding: 0; margin: 10px 20px 30px; @@ -361,43 +342,28 @@ li.col.username{ .col.username,.col.email,.col.userrole, .col.edit{ width: 100%; } -.settings .medium a.button{ - width: 100%; - display: block; - box-sizing:border-box; - text-align: center; +.userlist a, .userlist a:link, .userlist a:visited{ + color: #e0474c; + text-decoration: none; } -section{ - position: relative; - margin-bottom: 40px; - padding: 20px 20px 40px; - box-sizing: border-box; -} -header h1, header h2{ - display: inline-block; -} -header input[type="submit"]{ - margin-top: 1.5em; - margin-right: 10px; - float: right; - width: 150px; - box-sizing:border-box; - border: 0px; -} -.formWrapper, .welcome{ - display: block; - position: relative; - max-width: 900px; - margin: auto; +.userlist a:hover, .userlist a:focus, .userlist a:active{ + text-decoration: underline; } /********************* ** WELCOME PAGE ** *********************/ +.formWrapper, .welcome{ + display: block; + position: relative; + max-width: 900px; + margin: auto; +} .welcome{ padding: 30px 50px; box-sizing: border-box; + background: #FFFFFF; box-shadow: 0px 0px 10px rgba(0,0,0,0.2); } .welcome .welcomeIntro a, .welcome .welcomeCard a:link, .welcome .welcomeCard a:visited{ @@ -461,16 +427,15 @@ header input[type="submit"]{ height: 120px; } - -fieldset{ - display: block; - position: relative; - width: 100%; - margin: 0; - box-sizing: border-box; - border: none; - padding: 0; +header.headline +{ + padding: 0px 0px; } + +/******************** +* Forms * +********************/ + .large, .medium, .small{ padding: 18px 0px; box-sizing:border-box; @@ -483,6 +448,15 @@ form .large, form .medium, form .small{ .large img{ width: 100%; } +fieldset{ + display: block; + position: relative; + width: 100%; + margin: 0; + box-sizing: border-box; + border: none; + padding: 0; +} label,.label{ display: inline-block; width: 100%; @@ -492,68 +466,120 @@ label,.label{ line-height: 1.5em; text-transform: uppercase; } -label .help, .label .help{ - float:right; - width: 20px; - height: 20px; - line-height: 20px; - font-weight: bold; - border-radius: 50%; - text-align: center; - cursor: pointer; -} -input, select, textarea, .onoffswitch{ - margin-top: 5px; -} -input, select, textarea{ +input, select, textarea{ display: inline-block; box-shadow: none; width: 100%; font-size: 1em; box-sizing: border-box; min-height: 42px; + border: 1px solid #f9f8f6; + background: #f9f8f6; } -textarea{ - min-height: 84px; +input:focus, select:focus, textarea:focus{ + border: 1px solid #FFF; + outline: none; + box-shadow: 0 0 2px #000; } input, textarea{ padding: 10px; } -select{ - padding: 8px; +textarea{ + min-height: 84px; } -input[type="submit"]{ - border-radius: 3px; - margin-bottom: 40px; +select{ + padding: 14px 8px; } button,input[type="submit"]:hover{ cursor: pointer; } +input[type="submit"]{ + border-radius: 3px; + margin-bottom: 40px; + color: #f9f8f6; + border: 2px solid #e0474c; + background: #e0474c; +} +input[type="submit"]:hover{ + border: 2px solid #cc4146; + background: #cc4146; +} +input, select, button[type="button"]{ + min-height: 52px; +} +button[type="button"]{ + background: #f9f8f6; + color: #444; + border-top: 1px solid #ddd; + width: 100%; + text-align: center; +} +button[type="button"].active{ + background: #70c1b3; + color: #f9f8f6; + border-top: 1px solid #70c1b3; +} +button[type="button"].no-settings:hover{ + cursor: default; +} +button[type="button"].has-settings:hover{ + background: #efece7; +} +button[type="button"].has-settings.active:hover{ + background: #66b0a3; + border-top: 1px solid #66b0a3; +} +button[type="button"].plugin-button{ + border-left: 0px; + border-right: 0px; + border-bottom: 0px; + margin-top: 10px; + padding: 15px 0; +} +button[type="button"].theme-button{ + border: 0px; + margin: 5px 0 20px; + padding: 12px; + border-radius: 3px; + min-height: 52px; + background: #FFF; +} +button[type="button"].theme-button.active{ + background: #70c1b3; + color: #f9f8f6; +} +button[type="button"].theme-button:hover{ + border:0px; + border-top: 0px; +} + .button{ color: #fff; padding: 10px; border-radius: 3px; } +a.button, a.button:link,a.button:visited{ + text-decoration: none; + color: #fff; + border: 2px solid #e0474c; + background: #e0474c; +} +a.button:focus, a.button:hover, a.button:active{ + border: 2px solid #cc4146; + background: #cc4146; +} + +.settings .medium a.button{ + width: 100%; + display: block; + box-sizing:border-box; + text-align: center; +} /***************** ** CARDS ** *****************/ -header.headline -{ - padding: 0px 0px; -} -.userlist a, .userlist a:link, .userlist a:visited{ - color: #e0474c; - text-decoration: none; -} -.userlist a:hover, .userlist a:focus, .userlist a:active{ - text-decoration: underline; -} -a.button{ - color: #fff; - text-decoration: none; -} fieldset.card{ width: 100%; display: inline-block; @@ -561,34 +587,47 @@ fieldset.card{ box-sizing: border-box; vertical-align: top; } -.card > .cardInner{ - box-shadow: 0px 0px 5px rgba(0,0,0,0.2) +.card .medium{ + padding: 0px 20px; +} +.card img{ + width: 100%; + border: 1px solid #f9f8f6; + background: #f9f8f6; } .cardInner{ box-sizing: border-box; position: relative; + border: 1px solid #ddd; + background-color: #f9f8f6; +} +.cardInner .button-box{ + margin: 20px; } .cardHead h2{ - padding: 5px 20px 0; + padding: 5px 0px; margin-bottom: 0px; } .cardHead header, .cardHead p, .cardHead ul{ - padding: 15px 20px; + padding: 5px 0px; +} +.cardHead .cardActive{ + float: right; } .cardHead header{ width: 100%; height: 52px; display: inline-block; box-sizing: border-box; + padding: 20px; + background: #f9f8f6; + color: #444; } -.cardHead legend{ +.cardHead header legend{ font-size: 1.2em; font-weight: 700; float:left; } -.cardHead .cardActive{ - float: right; -} .cardHead header label{ display: inline-block; width: auto; @@ -608,41 +647,24 @@ fieldset.card{ vertical-align: middle; } .cardContent{ + padding: 20px 40px 0; +} +.cardDescription{ line-height: 1.5em; } -.card button[type="button"]{ - width: 100%; - text-align: center; -} -.card button[type="button"].plugin-button{ - border-left: 0px; - border-right: 0px; - border-bottom: 0px; - margin-top: 10px; - padding: 15px 0; -} -.card button[type="button"].theme-button{ - border: 0px; - margin: 5px 0 20px; - padding: 12px; - border-radius: 3px; - min-height: 42px; -} -.card button[type="button"].theme-button:hover{ - border:0px; -} -.card button[type="button"].no-settings:hover{ - cursor: default; -} -.card .medium{ - padding: 0px 20px; -} -.card .medium button[type="button"]{ - margin: 10px 0; +.cardDescPlugin{ + padding: 0 20px; } ul.cardInfo{ font-size: 0.8em; - padding: 0 20px; + padding: 0px; +} +.cardInfo a, .cardInfo a:link,.cardInfo a:visited{ + color: #70c1b3; + text-decoration: none; +} +.cardInfo a:focus,.cardInfo a:hover,.cardInfo a:active{ + text-decoration: underline; } .cardInfo li{ display: inline-block; @@ -660,96 +682,58 @@ ul.cardInfo{ max-height: 0; transition: max-height 0.5s ease-out; overflow: hidden; - padding: 0px 20px; + padding: 0px 40px; border: 0px; } .cardFields.open{ - max-height: 1800px; + max-height: 2800px; transition: max-height 0.5s ease-in; overflow: hidden; border: 1px; } -.cardFields label{ - margin-top: 12px; -} .cardFields .cardField:first-child{ margin-top: 50px; } .cardFields input, .cardFields select, .cardFields textarea, .cardFields .onoffswitch{ margin-bottom: 12px; } -.card img{ +.cardFields label{ + margin-top: 12px; +} +.cardFields fieldset.subfield legend{ + float:none; + font-weight: bold; + font-size: 1.2em; +} +.cardField{ + position: relative; width: 100%; + display: inline-block; + margin: 10px 0; } -.update-banner{ - display: none; - position: absolute; - right: 0; - width: 0; - height: 0; - border-top: 100px solid #e0474c; - border-bottom: 100px solid transparent; - border-left: 100px solid transparent; +.cardField label{ + width: 100%; + margin-top: 0px; + margin-left: 0px; + padding: 0px; + background: transparent; } -.settings .update-banner{ - top: 0px; +.cardField label.control-group{ + padding-left: 30px; + margin: 10px 0 0; } -.plugins .update-banner{ - top: 52px; +.cardField input, .cardField select, .cardField textarea{ + background-color: #FFF; } -.update-banner.show-banner{ - display: block; -} -.update-banner span{ - position: absolute; - top: -85px; - left: -55px; - text-align: center; - font-size: 0.9em; - transform: rotate(45deg); - display: block; - color: #fff; -} -.update-banner a{ - color: #fff; - text-decoration: none; +.cardFields fieldset.subfield{ + padding: 20px; + border: 1px solid #ddd; + margin: 20px 0 30px; } - -/* -** SELECT BUTTON -** https://codepen.io/vkjgr/pen/VYMeXp -*/ -select { - /* reset */ - -webkit-appearance: none; - -moz-appearance: none; - - /* style */ - background-image: - linear-gradient(45deg, transparent 50%, #444 50%), - linear-gradient(135deg, #444 50%, transparent 50%), - linear-gradient(to right, #f9f8f6, #f9f8f6); - background-position: - calc(100% - 20px) calc(1em + 2px), - calc(100% - 15px) calc(1em + 2px), - 100% 0; - background-size: - 5px 5px, - 5px 5px, - 2.8em 2.8em; - background-repeat: no-repeat; - cursor: pointer; -} -span.error{ - font-size: 0.8em; - line-height: 1em; -} -.cardInner span.error{ - display: block; - margin-top: -8px; - margin-bottom: 8px; -} +/******************** +* ALERT/ERRROR * +********************/ .alert{ color: #fff; @@ -767,80 +751,109 @@ span.error{ background: #e0474c; } -/* -** ON OFF SWITCH -** https://proto.io/freebies/onoff/ -*/ -.onoffswitch { - position: relative; - width: 90px; - -webkit-user-select:none; - -moz-user-select:none; - -ms-user-select:none; +.error input, +.error select, +.error textarea{ + border: 1px solid #e0474c; } -.onoffswitch-checkbox { - display: none; +.error, +.error label, +.error small, +.error p{ + color: #e0474c; } -.onoffswitch-label { - display: block; - overflow: hidden; - cursor: pointer; - border-radius: 20px; +.errors .cardHead header{ + background: #e0474c; + color: #fff; } -.onoffswitch-inner { - display: block; - width: 200%; - margin-left: -100%; - transition: margin 0.3s ease-in 0s; +span.error{ + font-size: 0.8em; + line-height: 1em; } -.onoffswitch-inner:before, .onoffswitch-inner:after { - display: block; - float: left; - width: 50%; - height: 38px; - padding: 0; - line-height: 38px; - font-size: 14px; - font-family: Trebuchet, Arial, sans-serif; - font-weight: bold; - box-sizing: border-box; -} -.onoffswitch-inner:before { - content: "ON"; - padding-left: 10px; -} -.onoffswitch-inner:after { - content: "OFF"; - padding-right: 10px; - text-align: right; -} -.onoffswitch-switch { - display: block; - width: 18px; - margin: 6px; - position: absolute; - top: 0; - bottom: 0; - right: 56px; - border-radius: 20px; - transition: all 0.3s ease-in 0s; -} -.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { - margin-left: 0; -} -.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { - right: 0px; +.cardInner span.error{ + display: block; + margin-top: -8px; + margin-bottom: 8px; } -/* -** COLOR PICKER -*/ + +/******************** +* UPDATE-BANNER * +********************/ + +.update-banner{ + display: none; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + border-top: 100px solid #e0474c; + border-bottom: 100px solid transparent; + border-right: 100px solid transparent; +} +.update-banner.show-banner{ + display: block; +} +.update-banner span{ + position: absolute; + top: -85px; + left: -20px; + text-align: center; + font-size: 0.9em; + transform: rotate(-45deg); + display: block; + color: #fff; + width: 100px; +} +.update-banner a{ + color: #fff; + text-decoration: none; +} + +/**************************************** +* SELECT BUTTON * +* https://codepen.io/vkjgr/pen/VYMeXp * +****************************************/ + +select{ + /* reset */ + -webkit-appearance: none; + -moz-appearance: none; + + /* style */ + background-image: + linear-gradient(45deg, transparent 50%, #444 50%), + linear-gradient(135deg, #444 50%, transparent 50%), + linear-gradient(to right, #f9f8f6, #f9f8f6); + background-position: + calc(100% - 20px) calc(1em + 6px), + calc(100% - 15px) calc(1em + 6px), + 100% 0; + background-size: + 5px 5px, + 5px 5px, + 2.8em 2.8em; + background-repeat: no-repeat; + cursor: pointer; +} +.cardField select{ + background-image: + linear-gradient(45deg, transparent 50%, #444 50%), + linear-gradient(135deg, #444 50%, transparent 50%), + linear-gradient(to right, #fff, #fff); +} +/******************** +* COLOR PICKER * +********************/ + .color-box{ width: 100%; height: 36px; margin-top: 5px; margin-bottom: 12px; display: inline-block; + border: 1px solid #ddd; } .color-box:hover{ cursor: pointer; @@ -852,11 +865,10 @@ span.error{ margin-top: 0px; } -/* -** CHECKBOX / RADIO -*/ +/************************ +* CHECKBOX / RADIO * +************************/ -/* Hide the browser's default checkbox */ .control-group input { position: absolute; opacity: 0; @@ -868,8 +880,6 @@ span.error{ font-size: 0.1em; min-height: 0px; } - -/* Customize the label (the container) */ .control-group { text-transform: none; display: block; @@ -890,67 +900,89 @@ span.error{ left: 0; height: 16px; width: 16px; + border: 2px solid #ccc; + background-color: #fff; border-radius: 2px; } -/* Create the checkmark/indicator (hidden when not checked) */ .checkmark:after { content: ""; position: absolute; display: none; } -/* Show the checkmark when checked */ -.control-group input:checked ~ .checkmark:after { - display: block; -} - -/* Style the checkmark/indicator */ .control-group .checkmark:after { - left: 5px; - top: 0px; - width: 5px; - height: 10px; - border-width: 0 2px 2px 0; - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); + left: 5px; + top: 0px; + width: 5px; + height: 10px; + border: solid #fff; + border-width: 0 2px 2px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); } -/* Create a custom radio button */ .radiomark { position: absolute; top: 0; left: 0; height: 16px; width: 16px; + border: 2px solid #ccc; + background-color: #fff; border-radius: 50%; } -/* Create the indicator (the dot/circle - hidden when not checked) */ .radiomark:after { content: ""; position: absolute; display: none; } -/* Show the indicator (dot/circle) when checked */ -.control-group input:checked ~ .radiomark:after { - display: block; -} -/* Style the indicator (dot/circle) */ .control-group .radiomark:after { top: 5px; left: 5px; width: 6px; height: 6px; + background: #fff; border-radius: 50%; } +.control-group:hover input ~ .checkmark, +.control-group:hover input ~ .radiomark { + background-color: #ccc; + border: 2px solid #ccc; +} +.control-group input:checked ~ .checkmark, +.control-group input:checked ~ .radiomark { + border: 2px solid #70c1b3; + background-color: #70c1b3; +} +.control-group input:checked ~ .checkmark:after, +.control-group input:checked ~ .radiomark:after { + display: block; +} +.control-group input:disabled ~ .checkmark, +.control-group input:disabled ~ .radiomark { + border: 2px solid #eee; + background-color: #eee; +} + +/******************** +* TOOLTIP * +********************/ + +label .help, .label .help{ + float:right; + width: 15px; + height: 15px; + line-height: 15px; + border-radius: 50%; + text-align: center; + background: #fff; + cursor: pointer; +} -/* TOOLTIP */ -/* Tooltip container */ .help { position: relative; display: inline-block; - border-bottom: 0px dotted black; /* If you want dots under the hoverable text */ + border-bottom: 0px dotted black; } - -/* Tooltip text */ .help .tooltip { visibility: hidden; width: 200px; @@ -959,8 +991,9 @@ span.error{ text-align: center; padding: 5px 0; border-radius: 6px; + color: #fff; + background-color: #555; - /* Position the tooltip text - see examples below! */ position: absolute; z-index: 1; opacity: 0; @@ -975,65 +1008,44 @@ span.error{ left: 100%; margin-top: -5px; border-width: 5px; + border-color: transparent transparent transparent #999; border-style: solid; } -/* Show the tooltip text when you mouse over the tooltip container */ -.help:hover .tooltip { +.help:hover .tooltip{ visibility: visible; opacity: 1; } +/******************** +* LOGIN * +********************/ +.loginarea{ + position: relative; + text-align: center; + width: 100%; +} +.loginbutton{ + height: 34px; +} +.loginarea .counter{ + position: absolute; + width: 100%; + top: 16px; + color: #444; +} +.loginarea .forgotpw{ + margin-top: 20px; +} +.loginarea .forgotpw a,.loginarea .forgotpw a:link,.loginarea .forgotpw a:visited{ + font-size: 0.8em; + text-decoration: none; + color: #cc4146; +} +.loginarea .forgotpw a:hover,.loginarea .forgotpw a:focus,.loginarea .forgotpw a:active{ + text-decoration: underline; +} -.version-banner{ - position:absolute; - display:block; - top:0; - right:0; - width:150px; - overflow:hidden; - height:150px; - z-index:9999; -} -.version-banner a{ - text-decoration:none; - font-family: calibri,"Helvetica Neue",arial,sans-serif; - text-align:center; - font-weight:300; - padding:5px 30px; - font-size:1rem; - line-height:1.5rem; - background:#e0474c; - color:#f9f8f6; - font-weight: 700; - width:150px; - position:absolute; - top:45px; - right:-45px; - transform:rotate(45deg); - -webkit-transform:rotate(45deg); - -ms-transform:rotate(45deg); - -moz-transform:rotate(45deg); - -o-transform:rotate(45deg); - box-shadow:1px 1px 2px rgba(0,0,0,0.8); -} -.version-banner a:hover{ - text-decoration: none; -} -.version-banner a::before,.version-banner a::after{ - content:""; - width:100%; - display:block; - position:absolute; - top:1px; - left:0; - height:1px; - background:#f9f8f6; -} -.version-banner a::after{ - bottom:1px; - top:auto; -} @media only screen and (min-width: 600px) { header.headline{ diff --git a/system/author/js/auth.js b/system/author/js/auth.js new file mode 100644 index 0000000..8ebb716 --- /dev/null +++ b/system/author/js/auth.js @@ -0,0 +1,32 @@ +/************************************* +** LOGIN TIMER ** +*************************************/ + +var wait = document.getElementById('wait'); + +if(wait) +{ + var loginbtn = document.getElementById("loginbutton"); + var seconds = parseInt(wait.innerHTML); + + loginbtn.disabled = true; + loginbtn.value = ''; + + var counter = setInterval(function () { + + seconds = seconds - 1; + wait.innerHTML = seconds; + + if (seconds == 0) { + loginbtn.disabled = false; + loginbtn.value = 'Login'; + var countdown = document.getElementById("counter"); + // var flash = document.getElementById("flash-message"); + + countdown.parentNode.removeChild(countdown); + // flash.parentNode.removeChild(flash); + + clearInterval(counter); + } + }, 1000); +} \ No newline at end of file diff --git a/system/author/js/author.js b/system/author/js/author.js index 8fab7c2..c8ee7b3 100644 --- a/system/author/js/author.js +++ b/system/author/js/author.js @@ -248,7 +248,7 @@ }); } } - + /************************************* ** COLOR PICKER ** *************************************/ diff --git a/system/author/layouts/layoutAuth.twig b/system/author/layouts/layoutAuth.twig index 870b004..af30e78 100644 --- a/system/author/layouts/layoutAuth.twig +++ b/system/author/layouts/layoutAuth.twig @@ -27,5 +27,6 @@ {% block content %}{% endblock %} + \ No newline at end of file diff --git a/system/author/partials/aside.twig b/system/author/partials/aside.twig index 818ae5b..787af2d 100644 --- a/system/author/partials/aside.twig +++ b/system/author/partials/aside.twig @@ -1,17 +1,19 @@ \ No newline at end of file diff --git a/system/author/partials/flash.twig b/system/author/partials/flash.twig index 9f994ab..3220138 100644 --- a/system/author/partials/flash.twig +++ b/system/author/partials/flash.twig @@ -1,6 +1,6 @@ {% if flash.getMessage('info') %} -
+
{{ flash.getMessage('info') | first }}
@@ -8,7 +8,7 @@ {% if messages.info %} -
+
{{ messages.info | first }}
@@ -16,7 +16,7 @@ {% if flash.getMessage('error') %} -
+
{{ flash.getMessage('error') | first }}
@@ -24,7 +24,7 @@ {% if messages.error %} -
+
{{ messages.error | first }}
diff --git a/system/author/partials/navi.twig b/system/author/partials/navi.twig index 5d49b23..ca8c0d6 100644 --- a/system/author/partials/navi.twig +++ b/system/author/partials/navi.twig @@ -4,8 +4,12 @@
\ No newline at end of file diff --git a/system/author/settings/plugins.twig b/system/author/settings/plugins.twig index 0a576a6..8d2cd4b 100644 --- a/system/author/settings/plugins.twig +++ b/system/author/settings/plugins.twig @@ -28,15 +28,17 @@
{{ plugin.version ? plugin.version : 'Unknown' }}
- -

{{ plugin.description ? plugin.description : 'No description' }}

- +
+

{{ plugin.description ? plugin.description : 'No description' }}

+ +
    +
  • {{ plugin.version ? plugin.version : 'Unknown' }}
  • + {{ plugin.licence ? plugin.licence : 'Unkown' }}
  • + by {{ plugin.author ? plugin.author : 'Unknown' }}
  • {% if plugin.homepage %}
  • + Web
  • {% endif %} +
+
{% for field in plugin.forms.fields %} diff --git a/system/author/settings/themes.twig b/system/author/settings/themes.twig index aad84cf..e7df82c 100644 --- a/system/author/settings/themes.twig +++ b/system/author/settings/themes.twig @@ -17,6 +17,7 @@
+
{% if theme.img %} @@ -24,39 +25,54 @@ {% else %}
No Preview
{% endif %} - -
{{ theme.version ? theme.version : 'Unknown' }}
-

{{ themeName }}

-

{{ theme.description }}

-
    -
  • {{ theme.version ? theme.version : 'Unknown' }}
  • - {{ theme.licence ? theme.licence : 'Unkown' }}
  • - by {{ theme.author ? theme.author : 'Unknown' }}
  • {% if theme.homepage %}
  • - Web
  • {% endif %} -
+
{{ theme.version ? theme.version : 'Unknown' }}
+ +
+

{{ themeName }}

+

{{ theme.description }}

+
    +
  • {{ theme.version ? theme.version : 'Unknown' }}
  • + {{ theme.licence ? theme.licence : 'Unkown' }}
  • + by {{ theme.author ? theme.author : 'Unknown' }}
  • {% if theme.homepage %}
  • + Web
  • {% endif %} +
+
- +
{% for field in theme.forms.fields %} - - {% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %} + + {% if field.type == 'fieldset' %} + +
+ {{ field.legend }} + {% for field in field.fields %} + {% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %} + {% endfor %} +
+ + {% else %} + + {% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %} + + {% endif %} {% endfor %} - +
- - - -
- -
-
- -
- +
+ + +
+ +
+
+ +
+
diff --git a/system/author/settings/user.twig b/system/author/settings/user.twig index 498ac5e..9ec45f2 100644 --- a/system/author/settings/user.twig +++ b/system/author/settings/user.twig @@ -9,7 +9,7 @@
-
+

Edit User

@@ -32,17 +32,19 @@ {% endif %}
-
- - - {% if errors.userrole %} - {{ errors.userrole | first }} - {% endif %} -
+ {% if is_role('administrator') %} +
+ + + {% if errors.userrole %} + {{ errors.userrole | first }} + {% endif %} +
+ {% endif %}
diff --git a/system/author/settings/userlist.twig b/system/author/settings/userlist.twig index bb8d467..a07edd7 100644 --- a/system/author/settings/userlist.twig +++ b/system/author/settings/userlist.twig @@ -7,7 +7,7 @@
-
+

All Users

diff --git a/system/author/settings/usernew.twig b/system/author/settings/usernew.twig index 09c293a..d8e2b8b 100644 --- a/system/author/settings/usernew.twig +++ b/system/author/settings/usernew.twig @@ -9,7 +9,7 @@
-
+

Create New User

diff --git a/system/system.php b/system/system.php index 13c3cd0..17315f7 100644 --- a/system/system.php +++ b/system/system.php @@ -7,6 +7,8 @@ use Typemill\Events\OnPluginsLoaded; * START SESSION * ************************/ +ini_set( 'session.cookie_httponly', 1 ); +session_name('typemill_session'); session_start(); /**************************** @@ -133,7 +135,7 @@ $container['view'] = function ($container) $path = array($container->get('settings')['themePath'], $container->get('settings')['authorPath']); $view = new \Slim\Views\Twig( $path, [ - 'cache' => false, + 'cache' => $container->get('settings')['cache'] ? $container->get('settings')['cachePath'] : false, 'autoescape' => false, 'debug' => true ]); @@ -143,6 +145,7 @@ $container['view'] = function ($container) $view->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath)); $view->addExtension(new Twig_Extension_Debug()); $view->addExtension(new Typemill\Extensions\TwigCsrfExtension($container['csrf'])); + $view->addExtension(new Typemill\Extensions\TwigUserExtension()); /* use {{ base_url() }} in twig templates */ $view['base_url'] = $container['request']->getUri()->getBaseUrl(); diff --git a/themes/typemill/partials/navigation.twig b/themes/typemill/partials/navigation.twig index 51c2aaf..566fe46 100644 --- a/themes/typemill/partials/navigation.twig +++ b/themes/typemill/partials/navigation.twig @@ -3,14 +3,17 @@ {% import _self as macros %} {% for element in navigation %} + + {% set depth = element.keyPathArray|length %} + {% if element.activeParent %} -
  • +
  • {% elseif element.active %} -
  • +
  • {% else %} -
  • +
  • {% endif %} - {% if element.elementType == 'folder' %} + {% if (element.elementType == 'folder') %} {{ element.name|title }}
      {{ macros.loop_over(element.folderContent) }} diff --git a/themes/typemill/typemill.yaml b/themes/typemill/typemill.yaml index 80f4b54..c609bfe 100644 --- a/themes/typemill/typemill.yaml +++ b/themes/typemill/typemill.yaml @@ -1,6 +1,6 @@ name: Typemill Theme -version: 1.0.6 -description: The standard theme for Typemill. Responsive, minimal and without any dependencies. It uses the system fonts Calibri and Helvetica. JavaScript is only used for code highlighting. Skip it, if you don't need it. +version: 1.0.7 +description: The standard theme for Typemill. Responsive, minimal and without any dependencies. It uses the system fonts Calibri and Helvetica. No JavaScript is used. author: Sebastian Schürmanns homepage: http://typemill.net licence: MIT @@ -17,43 +17,51 @@ forms: chapter: type: text - label: chapter + label: Text For Chapter placeholder: Add Name for Chapter required: true start: type: text - label: Start-Button + label: Label For Start Button placeholder: Add Label for Start-Button required: true - modified: - type: checkbox - label: Last Updated - description: Show last updated note at the end of each page? + fieldset1: + type: fieldset + legend: Last Modified + fields: + modified: + type: checkbox + label: Activate Last Modified + description: Show last modified date at the end of each page? - modifiedText: - type: text - label: Last Updated Text - placeholder: Last Updated + modifiedText: + type: text + label: Last Modified Text + placeholder: Last Updated - modifiedFormat: - type: select - label: Last Updated Format - placeholder: 'Add name of theme' - options: - 'm/d/Y': 01/20/2020 - 'd.m.Y': 20.01.2020 + modifiedFormat: + type: select + label: Last Modified Format + placeholder: 'Add name of theme' + options: + 'm/d/Y': 01/20/2020 + 'd.m.Y': 20.01.2020 - social: - type: checkbox - label: Share Links - description: Activate Share Links On Page? + fieldset2: + type: fieldset + legend: Share + fields: + social: + type: checkbox + label: Activate Share + description: Activate Share Buttons On Page? - socialbuttons: - type: checkboxlist - label: Select Share buttons - options: - facebook: Facebook - twitter: Twitter - xing: Xing \ No newline at end of file + socialbuttons: + type: checkboxlist + label: Select Share Buttons + options: + facebook: Facebook + twitter: Twitter + xing: Xing \ No newline at end of file