1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-04 21:27:41 +02:00

Version 1.1.6 User Role, Fieldsets and Refactoring

This commit is contained in:
Sebastian
2018-05-22 23:03:27 +02:00
parent aefb9709cb
commit e346625e32
32 changed files with 1073 additions and 657 deletions

7
.gitignore vendored
View File

@@ -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

2
cache/lastCache.txt vendored
View File

@@ -1 +1 @@
1526030510
1527021720

View File

@@ -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.
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.

View File

@@ -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.
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.

View File

@@ -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.

View File

@@ -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).
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).

View File

@@ -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
````

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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']))
{

View File

@@ -0,0 +1,34 @@
<?php
namespace Typemill\Extensions;
class TwigUserExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('is_role', array($this, 'isRole' )),
new \Twig_SimpleFunction('get_username', array($this, 'getUsername' ))
];
}
public function isRole($role)
{
if(isset($_SESSION['role']) && $_SESSION['role'] == $role)
{
return true;
}
return false;
}
public function getUsername()
{
if(isset($_SESSION['user']))
{
return $_SESSION['user'];
}
return false;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RedirectIfNoAdmin
{
protected $router;
public function __construct(RouterInterface $router, $flash)
{
$this->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);
}
}

View File

@@ -21,7 +21,7 @@ class RedirectIfUnauthenticated
{
$response = $response->withRedirect($this->router->pathFor('auth.show'));
}
return $next($request, $response);
}
}

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -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
];

View File

@@ -22,11 +22,18 @@
<span class="error">{{ errors.password | first }}</span>
{% endif %}
</div>
</fieldset>
</fieldset>
<div class="loginarea" id="loginarea">
<input type="submit" value="Login" id="loginbutton" class="loginbutton" />
{{ csrf_field() | raw }}
{% if messages.time %}
<div id="counter" class="counter">wait <span id="wait">{{ messages.time }}</span> sec</div>
<div class="forgotpw"><a href="https://typemill.net/writers/forgot-password" target="_blank">Forgot password?</a></div>
{% endif %}
</div>
<input type="submit" value="Login" />
{{ csrf_field() | raw }}
</form>
</div>

File diff suppressed because it is too large Load Diff

32
system/author/js/auth.js Normal file
View File

@@ -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);
}

View File

@@ -248,7 +248,7 @@
});
}
}
/*************************************
** COLOR PICKER **
*************************************/

View File

@@ -27,5 +27,6 @@
{% block content %}{% endblock %}
</div>
<script src="{{ base_url }}/system/author/js/auth.js"></script>
</body>
</html>

View File

@@ -1,17 +1,19 @@
<nav id="sidebar-menu" class="sidebar-menu">
<div id="mobile-menu" class="menu-action">Menu <span class="button-arrow"></span></div>
<h3>Settings</h3>
<ul class="menu-list">
<li class="menu-item"><a href="{{ path_for('settings.show') }}"{{ (route == 'settings.show') ? 'class="active"' : '' }}>System</a></li>
<li class="menu-item"><a href="{{ path_for('themes.show') }}"{{ (route == 'themes.show') ? 'class="active"' : '' }}>Themes</a></li>
<li class="menu-item"><a href="{{ path_for('plugins.show') }}"{{ (route == 'plugins.show') ? 'class="active"' : '' }}>Plugins</a></li>
</ul>
<h3>Users</h3>
<ul class="menu-list">
<li class="menu-item"><a href="{{ path_for('user.list') }}"{{ (route == 'user.list') ? 'class="active"' : '' }}>All users</a></li>
<li class="menu-item"><a href="{{ path_for('user.new') }}"{{ (route == 'user.new') ? 'class="active"' : '' }}>Create user</a></li>
{% for user in users %}
<li class="menu-item"><a href="{{ path_for('user.show', {'username' : user }) }}"{{ (username == user) ? 'class="active"' : '' }}>{{ user }}</a></li>
{% endfor %}
</ul>
{% if is_role('administrator') %}
<div id="mobile-menu" class="menu-action">Menu <span class="button-arrow"></span></div>
<h3>Settings</h3>
<ul class="menu-list">
<li class="menu-item"><a href="{{ path_for('settings.show') }}"{{ (route == 'settings.show') ? 'class="active"' : '' }}>System</a></li>
<li class="menu-item"><a href="{{ path_for('themes.show') }}"{{ (route == 'themes.show') ? 'class="active"' : '' }}>Themes</a></li>
<li class="menu-item"><a href="{{ path_for('plugins.show') }}"{{ (route == 'plugins.show') ? 'class="active"' : '' }}>Plugins</a></li>
</ul>
<h3>Users</h3>
<ul class="menu-list">
<li class="menu-item"><a href="{{ path_for('user.list') }}"{{ (route == 'user.list') ? 'class="active"' : '' }}>All users</a></li>
<li class="menu-item"><a href="{{ path_for('user.new') }}"{{ (route == 'user.new') ? 'class="active"' : '' }}>Create user</a></li>
{% for user in users %}
<li class="menu-item"><a href="{{ path_for('user.show', {'username' : user }) }}"{{ (username == user) ? 'class="active"' : '' }}>{{ user }}</a></li>
{% endfor %}
</ul>
{% endif %}
</nav>

View File

@@ -1,6 +1,6 @@
{% if flash.getMessage('info') %}
<div class="alert alert-info">
<div class="alert alert-info" id="flash-message">
{{ flash.getMessage('info') | first }}
</div>
@@ -8,7 +8,7 @@
{% if messages.info %}
<div class="alert alert-error">
<div class="alert alert-error" id="flash-message">
{{ messages.info | first }}
</div>
@@ -16,7 +16,7 @@
{% if flash.getMessage('error') %}
<div class="alert alert-error">
<div class="alert alert-error" id="flash-message">
{{ flash.getMessage('error') | first }}
</div>
@@ -24,7 +24,7 @@
{% if messages.error %}
<div class="alert alert-error">
<div class="alert alert-error" id="flash-message">
{{ messages.error | first }}
</div>

View File

@@ -4,8 +4,12 @@
</div>
<ul class="navi-items">
<li><a href="{{ path_for('content.show') }}"{{ navigation ? 'class="active"' : '' }}><i class="icon-doc-text"></i><span class="nav-label"> Content</span></a></li><li>
<a href="{{ path_for('settings.show') }}"{{ users ? 'class="active"' : '' }}><i class="icon-cog"></i><span class="nav-label"> Settings</span></a></li><li>
<a href="{{ base_url }}"><i class="icon-link-ext"></i><span class="nav-label"> Website</span></a></li><li>
{% if is_role('administrator') %}
<a href="{{ path_for('settings.show') }}"{{ users ? 'class="active"' : '' }}><i class="icon-cog"></i><span class="nav-label"> Settings</span></a></li><li>
{% else %}
<a href="{{ path_for('user.show', {'username' : get_username() }) }}"{{ users ? 'class="active"' : '' }}><i class="icon-cog"></i><span class="nav-label"> Account</span></a></li><li>
{% endif %}
<a href="{{ base_url }}"><i class="icon-link-ext"></i><span class="nav-label"> View Site</span></a></li><li>
<a href="{{ path_for('auth.logout') }}"><i class="icon-off"></i><span class="nav-label"> Logout</span></a></li>
</ul>
</nav>

View File

@@ -28,15 +28,17 @@
</div>
</header>
<div id="{{ pluginName }}" class="fc-plugin-version update-banner">{{ plugin.version ? plugin.version : 'Unknown' }}</div>
<p>{{ plugin.description ? plugin.description : 'No description' }}</p>
<ul class="cardInfo">
<li>{{ plugin.version ? plugin.version : 'Unknown' }}</li><li>
{{ plugin.licence ? plugin.licence : 'Unkown' }}</li><li>
by {{ plugin.author ? plugin.author : 'Unknown' }}</li>{% if plugin.homepage %}<li>
<a href="{{ plugin.homepage}}" target="blank">Web</a></li>{% endif %}
</ul>
<div class="cardDescPlugin">
<p>{{ plugin.description ? plugin.description : 'No description' }}</p>
<ul class="cardInfo">
<li>{{ plugin.version ? plugin.version : 'Unknown' }}</li><li>
{{ plugin.licence ? plugin.licence : 'Unkown' }}</li><li>
by {{ plugin.author ? plugin.author : 'Unknown' }}</li>{% if plugin.homepage %}<li>
<a href="{{ plugin.homepage}}" target="blank">Web</a></li>{% endif %}
</ul>
</div>
<div class="cardInner cardFields{{ errors[pluginName] ? ' open' : '' }}">
{% for field in plugin.forms.fields %}

View File

@@ -17,6 +17,7 @@
<form method="POST" action="{{ path_for('themes.save') }}">
<fieldset class="card{{ errors[themeName] ? ' errors' : '' }}">
<div class="cardInner cardHead">
{% if theme.img %}
@@ -24,39 +25,54 @@
{% else %}
<div class="no-preview">No Preview</div>
{% endif %}
<div id="{{ themeName }}" class="fc-theme-version update-banner">{{ theme.version ? theme.version : 'Unknown' }}</div>
<div class="cardContent">
<h2>{{ themeName }}</h2>
<p>{{ theme.description }}</p>
<ul class="cardInfo">
<li>{{ theme.version ? theme.version : 'Unknown' }}</li><li>
{{ theme.licence ? theme.licence : 'Unkown' }}</li><li>
by {{ theme.author ? theme.author : 'Unknown' }}</li>{% if theme.homepage %}<li>
<a href="{{ theme.homepage}}" target="blank">Web</a></li>{% endif %}
</ul>
<div id="{{ themeName }}" class="fc-theme-version update-banner">{{ theme.version ? theme.version : 'Unknown' }}</div>
<div class="cardDescription">
<h2>{{ themeName }}</h2>
<p>{{ theme.description }}</p>
<ul class="cardInfo">
<li>{{ theme.version ? theme.version : 'Unknown' }}</li><li>
{{ theme.licence ? theme.licence : 'Unkown' }}</li><li>
by {{ theme.author ? theme.author : 'Unknown' }}</li>{% if theme.homepage %}<li>
<a href="{{ theme.homepage}}" target="blank">Web</a></li>{% endif %}
</ul>
</div>
</div>
<div class="cardInner cardFields{{ errors[themeName] ? ' open' : '' }}">
{% for field in theme.forms.fields %}
{% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %}
{% if field.type == 'fieldset' %}
<fieldset class="subfield">
<legend>{{ field.legend }}</legend>
{% for field in field.fields %}
{% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %}
{% endfor %}
</fieldset>
{% else %}
{% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %}
{% endif %}
{% endfor %}
</div>
<input type="hidden" name="theme" value="{{ themeName }}">
<div class="medium">
<button type="button" class="theme-button fc-settings{{ (settings.theme == themeName) ? ' active' : '' }}{{ theme.forms.fields|length > 0 ? ' has-settings' : ' no-settings'}}">{{ theme.forms.fields|length > 0 ? 'Settings <span class="button-arrow"></span>' : 'No Settings'}}</button>
</div>
<div class="medium">
<input type="submit" value="Save Theme" />
</div>
<div class="button-box">
<input type="hidden" name="theme" value="{{ themeName }}">
<div class="medium">
<button type="button" class="theme-button fc-settings{{ (settings.theme == themeName) ? ' active' : '' }}{{ theme.forms.fields|length > 0 ? ' has-settings' : ' no-settings'}}">{{ theme.forms.fields|length > 0 ? 'Settings <span class="button-arrow"></span>' : 'No Settings'}}</button>
</div>
<div class="medium">
<input type="submit" value="Save Theme" />
</div>
</div>
</div>
</fieldset>

View File

@@ -9,7 +9,7 @@
<section id="user" class="settings">
<header>
<header class="headline">
<h1>Edit User</h1>
</header>
@@ -32,17 +32,19 @@
{% endif %}
</div>
<div class="large{{ errors.userrole ? ' errors' : '' }}">
<label for="userrole">Role <abbr title="required">*</abbr></label>
<select name="userrole" required>
{% for role in userrole %}
<option value="{{ role }}"{% if (role == old.userrole or role == userdata.userrole) %} selected{% endif %}>{{ role }}</option>
{% endfor %}
</select>
{% if errors.userrole %}
<span class="error">{{ errors.userrole | first }}</span>
{% endif %}
</div>
{% if is_role('administrator') %}
<div class="large{{ errors.userrole ? ' errors' : '' }}">
<label for="userrole">Role <abbr title="required">*</abbr></label>
<select name="userrole" required>
{% for role in userrole %}
<option value="{{ role }}"{% if (role == old.userrole or role == userdata.userrole) %} selected{% endif %}>{{ role }}</option>
{% endfor %}
</select>
{% if errors.userrole %}
<span class="error">{{ errors.userrole | first }}</span>
{% endif %}
</div>
{% endif %}
<div class="large{{ errors.password ? ' errors' : '' }}">
<label for="password">Actual Password</label>

View File

@@ -7,7 +7,7 @@
<section id="users" class="settings">
<header>
<header class="headline">
<h1>All Users</h1>
</header>

View File

@@ -9,7 +9,7 @@
<section id="new-user" class="settings">
<header>
<header class="headline">
<h1>Create New User</h1>
</header>

View File

@@ -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();

View File

@@ -3,14 +3,17 @@
{% import _self as macros %}
{% for element in navigation %}
{% set depth = element.keyPathArray|length %}
{% if element.activeParent %}
<li class="{{ element.elementType }} level-{{ element.keyPathArray|length }} active parent">
<li class="{{ element.elementType }} level-{{ depth }} active parent">
{% elseif element.active %}
<li class="{{ element.elementType }} level-{{ element.keyPathArray|length }} active">
<li class="{{ element.elementType }} level-{{ depth }} active">
{% else %}
<li class="{{ element.elementType }} level-{{ element.keyPathArray|length }}">
<li class="{{ element.elementType }} level-{{ depth }}">
{% endif %}
{% if element.elementType == 'folder' %}
{% if (element.elementType == 'folder') %}
<a href="{{ element.urlAbs }}">{{ element.name|title }}</a>
<ul>
{{ macros.loop_over(element.folderContent) }}

View File

@@ -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
socialbuttons:
type: checkboxlist
label: Select Share Buttons
options:
facebook: Facebook
twitter: Twitter
xing: Xing