Bladeren bron

Version 1.3.8: ACL Integration

trendschau 5 jaren geleden
bovenliggende
commit
4f80e07810
44 gewijzigde bestanden met toevoegingen van 878 en 421 verwijderingen
  1. 6 1
      content/01-cyanine-theme/03-content-elements.yaml
  2. BIN
      media/live/hostinger-1.png
  3. BIN
      media/live/hostinger-2.png
  4. BIN
      media/live/hostinger.png
  5. BIN
      media/live/logo.png
  6. BIN
      media/original/hostinger-1.png
  7. BIN
      media/original/hostinger-2.png
  8. BIN
      media/original/hostinger.png
  9. BIN
      media/original/logo.png
  10. BIN
      media/thumbs/hostinger-1.png
  11. BIN
      media/thumbs/hostinger-2.png
  12. BIN
      media/thumbs/hostinger.png
  13. BIN
      media/thumbs/logo.png
  14. 154 8
      system/Controllers/ArticleApiController.php
  15. 9 4
      system/Controllers/AuthController.php
  16. 76 11
      system/Controllers/BlockApiController.php
  17. 26 2
      system/Controllers/ContentBackendController.php
  18. 19 0
      system/Controllers/ContentController.php
  19. 12 0
      system/Controllers/MediaApiController.php
  20. 16 0
      system/Controllers/MetaApiController.php
  21. 304 275
      system/Controllers/SettingsController.php
  22. 3 3
      system/Controllers/SetupController.php
  23. 14 0
      system/Events/OnSystemnaviLoaded.php
  24. 14 0
      system/Events/OnUserfieldsLoaded.php
  25. 10 0
      system/Extensions/TwigUserExtension.php
  26. 3 0
      system/Middleware/RedirectIfNoAdmin.php
  27. 22 10
      system/Models/User.php
  28. 17 1
      system/Models/WriteMeta.php
  29. 11 9
      system/Routes/Web.php
  30. 10 12
      system/Settings.php
  31. 1 1
      system/Translations.php
  32. 31 2
      system/author/css/style.css
  33. 16 8
      system/author/editor/editor-blox.twig
  34. 4 0
      system/author/editor/editor-raw.twig
  35. 38 21
      system/author/editor/publish-controller.twig
  36. 5 1
      system/author/js/vue-blox.js
  37. 4 0
      system/author/js/vue-meta.js
  38. 7 8
      system/author/js/vue-publishcontroller.js
  39. 7 2
      system/author/metatabs.yaml
  40. 10 10
      system/author/partials/aside.twig
  41. 9 4
      system/author/partials/navi.twig
  42. 2 2
      system/author/settings/user.twig
  43. 8 0
      system/author/settings/userlist.twig
  44. 10 26
      system/author/settings/usernew.twig

+ 6 - 1
content/01-cyanine-theme/03-content-elements.yaml

@@ -1,8 +1,13 @@
 meta:
 meta:
     title: 'Content Elements'
     title: 'Content Elements'
     description: "There are a lot of other settings for your content area. For example:  \nAdd an edit-button for github, gitlab or other plattforms.\nShow the author.\nShow the publish date.\nShow the chapter numbers in the navigation.\n"
     description: "There are a lot of other settings for your content area. For example:  \nAdd an edit-button for github, gitlab or other plattforms.\nShow the author.\nShow the publish date.\nShow the chapter numbers in the navigation.\n"
+    heroimage: null
+    heroimagealt: null
+    owner: testauthor
     author: trendschau
     author: trendschau
+    manualdate: null
+    modified: '2020-07-09'
     created: '2020-06-11'
     created: '2020-06-11'
     time: 21-05-02
     time: 21-05-02
     navtitle: 'content elements'
     navtitle: 'content elements'
-    modified: '2020-06-11'
+    hide: false

BIN
media/live/hostinger-1.png


BIN
media/live/hostinger-2.png


BIN
media/live/hostinger.png


BIN
media/live/logo.png


BIN
media/original/hostinger-1.png


BIN
media/original/hostinger-2.png


BIN
media/original/hostinger.png


BIN
media/original/logo.png


BIN
media/thumbs/hostinger-1.png


BIN
media/thumbs/hostinger-2.png


BIN
media/thumbs/hostinger.png


BIN
media/thumbs/logo.png


+ 154 - 8
system/Controllers/ArticleApiController.php

@@ -24,6 +24,12 @@ class ArticleApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user can publish his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'publish'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
+		}
+
 		# validate input only if raw mode
 		# validate input only if raw mode
 		if($this->params['raw'])
 		if($this->params['raw'])
 		{
 		{
@@ -35,6 +41,16 @@ class ArticleApiController extends ContentController
 
 
 		# set item 
 		# set item 
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
+
+		# if user has no right to update content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'publish'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
+			}
+		}
 		
 		
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
@@ -100,12 +116,28 @@ class ArticleApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user can unpublish his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'unpublish'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to unpublish content.']), 403);
+		}
+
 		# set structure
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
 		if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
 
 
 		# set item 
 		# set item 
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 
 
+		# if user has no right to update content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'unpublish'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to unpublish content.']), 403);
+			}
+		}
+
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
 
 
@@ -178,17 +210,32 @@ class ArticleApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		
 		
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
+		}
+
 		# set structure
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
 		if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
 
 
 		# set item
 		# set item
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		
 		
+		# if user has no right to update content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to update content.']), 403);
+			}
+		}
+
 		# remove the unpublished changes
 		# remove the unpublished changes
 		$delete = $this->deleteContentFiles(['txt']);
 		$delete = $this->deleteContentFiles(['txt']);
 
 
 		# set redirect url to edit page
 		# set redirect url to edit page
-
 		$url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'];
 		$url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'];
 		if(isset($this->item->urlRelWoF))
 		if(isset($this->item->urlRelWoF))
 		{
 		{
@@ -217,6 +264,12 @@ class ArticleApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to delete his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'delete'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete content.']), 403);
+		}
+
 		# set url to base path initially
 		# set url to base path initially
 		$url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'];
 		$url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'];
 		
 		
@@ -225,6 +278,16 @@ class ArticleApiController extends ContentController
 
 
 		# set item
 		# set item
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
+
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete content.']), 403);
+			}
+		}
 		
 		
 		if($this->item->elementType == 'file')
 		if($this->item->elementType == 'file')
 		{
 		{
@@ -275,16 +338,32 @@ class ArticleApiController extends ContentController
 		# get params from call 
 		# get params from call 
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
+
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to update content.']), 403);
+		}
 		
 		
 		# validate input 
 		# validate input 
 		if(!$this->validateEditorInput()){ return $response->withJson($this->errors,422); }
 		if(!$this->validateEditorInput()){ return $response->withJson($this->errors,422); }
 		
 		
 		# set structure
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
 		if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
-				
+
 		# set item 
 		# set item 
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 
 
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to update content.']), 403);
+			}
+		}
+
 		# set path for the file (or folder)
 		# set path for the file (or folder)
 		$this->setItemPath('txt');
 		$this->setItemPath('txt');
 
 
@@ -319,6 +398,12 @@ class ArticleApiController extends ContentController
 		# get params from call
 		# get params from call
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
+
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to update content.'), 403);
+		}
 		
 		
 		# url is only needed, if an active page is moved to another folder, so user has to be redirected to the new url
 		# url is only needed, if an active page is moved to another folder, so user has to be redirected to the new url
 		$url 			= false;
 		$url 			= false;
@@ -339,6 +424,19 @@ class ArticleApiController extends ContentController
 
 
 		if(!$item){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
 		if(!$item){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
 		
 		
+		# needed for acl check
+		$this->item = $item;
+
+		# if user has no right to update content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => $this->structure, 'errors'  => 'You are not allowed to move that content.'), 403);
+			}
+		}
+
 		# if an item is moved to the first level
 		# if an item is moved to the first level
 		if($this->params['parent_id_to'] == 'navi')
 		if($this->params['parent_id_to'] == 'navi')
 		{
 		{
@@ -397,7 +495,7 @@ class ArticleApiController extends ContentController
 			}
 			}
 			$index++;
 			$index++;
 		}
 		}
-		if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404); }
+		if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => ['message' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.'], 'url' => $url), 404); }
 
 
 		# update the structure for editor
 		# update the structure for editor
 		$this->setStructure($draft = true, $cache = false);
 		$this->setStructure($draft = true, $cache = false);
@@ -427,6 +525,12 @@ class ArticleApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'create'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to create content.']), 403);
+		}
+
 		# url is only needed, if an active page is moved
 		# url is only needed, if an active page is moved
 		$url 			= false;
 		$url 			= false;
 		
 		
@@ -434,7 +538,7 @@ class ArticleApiController extends ContentController
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
 		
 		
 		# validate input
 		# validate input
-		if(!$this->validateNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 60 chars.', 'url' => $url), 422); }
+		if(!$this->validateNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => ['message' => 'Special Characters not allowed. Length between 1 and 60 chars.'], 'url' => $url), 422); }
 		
 		
 		# get the ids (key path) for item, old folder and new folder
 		# get the ids (key path) for item, old folder and new folder
 		$folderKeyPath 	= explode('.', $this->params['folder_id']);
 		$folderKeyPath 	= explode('.', $this->params['folder_id']);
@@ -442,7 +546,7 @@ class ArticleApiController extends ContentController
 		# get the item from structure
 		# get the item from structure
 		$folder			= Folder::getItemWithKeyPath($this->structure, $folderKeyPath);
 		$folder			= Folder::getItemWithKeyPath($this->structure, $folderKeyPath);
 
 
-		if(!$folder){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
+		if(!$folder){ return $response->withJson(array('data' => $this->structure, 'errors' => ['message' => 'We could not find this page. Please refresh and try again.'], 'url' => $url), 404); }
 				
 				
 		$name 		= $this->params['item_name'];
 		$name 		= $this->params['item_name'];
 		$slug 		= URLify::filter(iconv(mb_detect_encoding($this->params['item_name'], mb_detect_order(), true), "UTF-8", $this->params['item_name']));
 		$slug 		= URLify::filter(iconv(mb_detect_encoding($this->params['item_name'], mb_detect_order(), true), "UTF-8", $this->params['item_name']));
@@ -464,7 +568,6 @@ class ArticleApiController extends ContentController
 			return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
 			return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
 		}
 		}
 
 
-
 		# get extended structure
 		# get extended structure
 		$extended 	= $write->getYaml('cache', 'structure-extended.yaml');
 		$extended 	= $write->getYaml('cache', 'structure-extended.yaml');
 
 
@@ -494,6 +597,12 @@ class ArticleApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'create'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to create content.']), 403);
+		}
+
 		# url is only needed, if an active page is moved
 		# url is only needed, if an active page is moved
 		$url 			= false;
 		$url 			= false;
 		
 		
@@ -608,6 +717,12 @@ class ArticleApiController extends ContentController
 		# get params from call
 		# get params from call
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
+
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'create'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to create content.'), 403);
+		}
 		
 		
 		# url is only needed, if an active page is moved
 		# url is only needed, if an active page is moved
 		$url 			= false;
 		$url 			= false;
@@ -730,12 +845,28 @@ class ArticleApiController extends ContentController
 		/* get params from call */
 		/* get params from call */
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
-		
+
+		# minimum permission is that user is allowed to update his own content. This will completely disable the block-editor
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to edit content.'), 403);
+		}
+
 		# set structure
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
 		
 		
 		/* set item */
 		/* set item */
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
+
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete content.'), 403);
+			}
+		}
 		
 		
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
@@ -778,12 +909,27 @@ class ArticleApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		
 		
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to edit content.'), 403);
+		}
+
 		# set structure
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
 		
 		
 		/* set item */
 		/* set item */
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
-		
+
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete content.'), 403);
+			}
+		}
+
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
 
 

+ 9 - 4
system/Controllers/AuthController.php

@@ -125,10 +125,15 @@ class AuthController extends Controller
 					$yaml->updateYaml('settings/users', '.logins', $logins);					
 					$yaml->updateYaml('settings/users', '.logins', $logins);					
 				}
 				}
 
 
-				$settings = $this->c->get('settings');
-				$editor = (isset($settings['editor']) && $settings['editor'] == 'visual') ? 'visual' : 'raw';
-				
-				return $response->withRedirect($this->c->router->pathFor('content.' . $editor));
+				# if user is allowed to view content-area
+				if($this->c->acl->isAllowed($userdata['userrole'], 'content', 'view'))
+				{
+					$settings = $this->c->get('settings');
+					$editor = (isset($settings['editor']) && $settings['editor'] == 'visual') ? 'visual' : 'raw';
+					
+					return $response->withRedirect($this->c->router->pathFor('content.' . $editor));
+				}
+				return $response->withRedirect($this->c->router->pathFor('user.account'));
 			}
 			}
 		}
 		}
 
 

+ 76 - 11
system/Controllers/BlockApiController.php

@@ -21,6 +21,12 @@ class BlockApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
+		}
+
 		/* validate input */
 		/* validate input */
 		if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
 		if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
 		
 		
@@ -30,6 +36,16 @@ class BlockApiController extends ContentController
 		/* set item */
 		/* set item */
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 
 
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to edit content.']), 403);
+			}
+		}
+
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
 
 
@@ -77,7 +93,7 @@ class BlockApiController extends ContentController
 		elseif(($this->params['block_id'] == 0) OR !isset($pageMarkdown[$this->params['block_id']]))
 		elseif(($this->params['block_id'] == 0) OR !isset($pageMarkdown[$this->params['block_id']]))
 		{
 		{
 			# if the block does not exists, return an error
 			# if the block does not exists, return an error
-			return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'The ID of the content-block is wrong.']), 404);
 		}
 		}
 		else
 		else
 		{
 		{
@@ -201,6 +217,12 @@ class BlockApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
+		}
+
 		/* validate input */
 		/* validate input */
 		if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
 		if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
 		
 		
@@ -210,6 +232,16 @@ class BlockApiController extends ContentController
 		/* set item */
 		/* set item */
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 
 
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to edit content.']), 403);
+			}
+		}
+
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
 
 
@@ -249,7 +281,7 @@ class BlockApiController extends ContentController
 		if(!isset($pageMarkdown[$this->params['block_id']]))
 		if(!isset($pageMarkdown[$this->params['block_id']]))
 		{
 		{
 			# if the block does not exists, return an error
 			# if the block does not exists, return an error
-			return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'The ID of the content-block is wrong.']), 404);
 		}
 		}
 		elseif($this->params['block_id'] == 0)
 		elseif($this->params['block_id'] == 0)
 		{
 		{
@@ -340,6 +372,12 @@ class BlockApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
+		}
+
 		# validate input 
 		# validate input 
 		# if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
 		# if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
 		
 		
@@ -349,6 +387,16 @@ class BlockApiController extends ContentController
 		# set item 
 		# set item 
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 
 
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete content.']), 403);
+			}
+		}
+
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
 
 
@@ -382,7 +430,7 @@ class BlockApiController extends ContentController
 		if(!isset($pageMarkdown[$oldIndex]))
 		if(!isset($pageMarkdown[$oldIndex]))
 		{
 		{
 			# if the block does not exists, return an error
 			# if the block does not exists, return an error
-			return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'The ID of the content-block is wrong.']), 404);
 		}
 		}
 
 
 		$extract = array_splice($pageMarkdown, $oldIndex, 1);
 		$extract = array_splice($pageMarkdown, $oldIndex, 1);
@@ -432,6 +480,12 @@ class BlockApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$errors			= false;
 		$errors			= false;
+
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
+		}
 		
 		
 		# set structure
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
@@ -439,6 +493,16 @@ class BlockApiController extends ContentController
 		# set item
 		# set item
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 
 
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete content.']), 403);
+			}
+		}
+
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
 
 
@@ -616,7 +680,7 @@ class BlockApiController extends ContentController
 			return $response->withJson(array('errors' => false));	
 			return $response->withJson(array('errors' => false));	
 		}
 		}
 
 
-		return $response->withJson(array('errors' => 'could not store image to temporary folder'));	
+		return $response->withJson(array('errors' => ['message' => 'could not store image to temporary folder']));	
 	}
 	}
 
 
 	public function createFile(Request $request, Response $response, $args)
 	public function createFile(Request $request, Response $response, $args)
@@ -632,7 +696,7 @@ class BlockApiController extends ContentController
 		$allowedMimes = $this->getAllowedMtypes();
 		$allowedMimes = $this->getAllowedMtypes();
 		if(!in_array($mtype, $allowedMimes))
 		if(!in_array($mtype, $allowedMimes))
 		{
 		{
-			return $response->withJson(array('errors' => 'File-type is not allowed'));
+			return $response->withJson(array('errors' => ['message' => 'File-type is not allowed']));
 		}
 		}
 
 
 		# sanitize file name
 		# sanitize file name
@@ -653,7 +717,7 @@ class BlockApiController extends ContentController
 			return $response->withJson(array('errors' => false, 'name' => $name));
 			return $response->withJson(array('errors' => false, 'name' => $name));
 		}
 		}
 
 
-		return $response->withJson(array('errors' => 'could not store file to temporary folder'));
+		return $response->withJson(array('errors' => ['message' => 'could not store file to temporary folder']));
 	}
 	}
 	
 	
 	public function publishImage(Request $request, Response $response, $args)
 	public function publishImage(Request $request, Response $response, $args)
@@ -681,7 +745,7 @@ class BlockApiController extends ContentController
 			return $this->updateBlock($request, $response, $args);
 			return $this->updateBlock($request, $response, $args);
 		}
 		}
 
 
-		return $response->withJson(array('errors' => 'could not store image to media folder'));	
+		return $response->withJson(array('errors' => ['message' => 'could not store image to media folder']));	
 	}
 	}
 
 
 	public function deleteImage(Request $request, Response $response, $args)
 	public function deleteImage(Request $request, Response $response, $args)
@@ -692,7 +756,7 @@ class BlockApiController extends ContentController
 
 
 		if(!isset($this->params['name']))
 		if(!isset($this->params['name']))
 		{
 		{
-			return $response->withJson(array('errors' => 'image name is missing'));	
+			return $response->withJson(array('errors' => ['message' => 'image name is missing']));	
 		}
 		}
 
 
 		$imageProcessor	= new ProcessImage($this->settings['images']);
 		$imageProcessor	= new ProcessImage($this->settings['images']);
@@ -714,7 +778,7 @@ class BlockApiController extends ContentController
 
 
 		if(!isset($this->params['name']))
 		if(!isset($this->params['name']))
 		{
 		{
-			return $response->withJson(array('errors' => 'file name is missing'));	
+			return $response->withJson(array('errors' => ['message' => 'file name is missing']));	
 		}
 		}
 
 
 		$fileProcessor	= new ProcessFile();
 		$fileProcessor	= new ProcessFile();
@@ -725,7 +789,7 @@ class BlockApiController extends ContentController
 			return $response->withJson(array('errors' => false));
 			return $response->withJson(array('errors' => false));
 		}
 		}
 
 
-		return $response->withJson(array('errors' => 'could not delete the file'));
+		return $response->withJson(array('errors' => ['message' => 'could not delete the file']));
 	}
 	}
 
 
 	public function saveVideoImage(Request $request, Response $response, $args)
 	public function saveVideoImage(Request $request, Response $response, $args)
@@ -803,7 +867,7 @@ class BlockApiController extends ContentController
 			return $this->updateBlock($request, $response, $args);
 			return $this->updateBlock($request, $response, $args);
 		}
 		}
 		
 		
-		return $response->withJson(array('errors' => 'could not store the preview image'));	
+		return $response->withJson(array('errors' => ['message' => 'could not store the preview image']));	
 	}
 	}
 
 
 	private function getAllowedMtypes()
 	private function getAllowedMtypes()
@@ -828,6 +892,7 @@ class BlockApiController extends ContentController
 			'application/pdf',
 			'application/pdf',
 		   	'image/png',
 		   	'image/png',
 		   	'image/jpeg',
 		   	'image/jpeg',
+		   	'image/jpg',
 		   	'image/gif',
 		   	'image/gif',
 		   	'image/svg+xml',
 		   	'image/svg+xml',
 		   	'font/*',
 		   	'font/*',

+ 26 - 2
system/Controllers/ContentBackendController.php

@@ -32,6 +32,9 @@ class ContentBackendController extends ContentController
 
 
 		# set item
 		# set item
 		if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
 		if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
+
+		# we have to check ownership here to use it for permission-check in tempates
+		$this->checkContentOwnership();
 		
 		
 		# get the breadcrumb (here we need it only to mark the actual item active in navigation)
 		# get the breadcrumb (here we need it only to mark the actual item active in navigation)
 		$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $this->item->keyPathArray) : false;
 		$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $this->item->keyPathArray) : false;
@@ -75,7 +78,16 @@ class ContentBackendController extends ContentController
 			}
 			}
 		}
 		}
 
 
-		return $this->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
+		return $this->render($response, 'editor/editor-raw.twig', array(
+			'acl'			=> $this->c->acl,
+			'mycontent'		=> $this->mycontent,
+			'navigation' 	=> $this->structure, 
+			'homepage' 		=> $this->homepage, 
+			'title' 		=> $title, 
+			'content' 		=> $content, 
+			'item' 			=> $this->item, 
+			'settings' 		=> $this->settings
+		));
 	}
 	}
 	
 	
 	/**
 	/**
@@ -101,6 +113,9 @@ class ContentBackendController extends ContentController
 		# set item
 		# set item
 		if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
 		if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
 
 
+		# we have to check ownership here to use it for permission-check in tempates
+		$this->checkContentOwnership();
+
 		# set the status for published and drafted
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		$this->setPublishStatus();
 		
 		
@@ -153,7 +168,16 @@ class ContentBackendController extends ContentController
 			unset($content[0]);			
 			unset($content[0]);			
 		}
 		}
 
 
-		return $this->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
+		return $this->render($response, 'editor/editor-blox.twig', array(
+			'acl'			=> $this->c->acl, 
+			'mycontent'		=> $this->mycontent,
+			'navigation' 	=> $this->structure,
+			'homepage' 		=> $this->homepage, 
+			'title' 		=> $title, 
+			'content' 		=> $content, 
+			'item' 			=> $this->item, 
+			'settings' 		=> $this->settings 
+		));
 	}
 	}
 	
 	
 	public function showEmpty(Request $request, Response $response, $args)
 	public function showEmpty(Request $request, Response $response, $args)

+ 19 - 0
system/Controllers/ContentController.php

@@ -10,6 +10,7 @@ use Typemill\Models\Folder;
 use Typemill\Models\Write;
 use Typemill\Models\Write;
 use Typemill\Models\WriteCache;
 use Typemill\Models\WriteCache;
 use Typemill\Models\WriteYaml;
 use Typemill\Models\WriteYaml;
+use Typemill\Models\WriteMeta;
 
 
 abstract class ContentController
 abstract class ContentController
 {
 {
@@ -51,6 +52,9 @@ abstract class ContentController
 	
 	
 	# holds the content of the page
 	# holds the content of the page
 	protected $content;
 	protected $content;
+
+	# holds the ownership (my content or not my content)
+	protected $mycontent = false;
 	
 	
 	public function __construct(ContainerInterface $c)
 	public function __construct(ContainerInterface $c)
 	{
 	{
@@ -432,4 +436,19 @@ abstract class ContentController
 		$this->content = $content;
 		$this->content = $content;
 		return true;		
 		return true;		
 	}
 	}
+
+	protected function checkContentOwnership()
+	{
+		# get page meta
+		$writeMeta = new writeMeta();
+		$pagemeta = $writeMeta->getPageMeta($this->settings, $this->item);
+
+		# owner assertion, not 
+		if(isset($pagemeta['meta']['owner']) && $pagemeta['meta']['owner'] == $_SESSION['user'])
+		{
+			$this->mycontent = true;
+			return true;
+		}
+		return false;
+	}		
 }
 }

+ 12 - 0
system/Controllers/MediaApiController.php

@@ -207,6 +207,12 @@ class MediaApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to delete content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403);
+		}
+
 		if(!isset($this->params['name']))
 		if(!isset($this->params['name']))
 		{
 		{
 			return $response->withJson(['errors' => 'image name is missing'],500);
 			return $response->withJson(['errors' => 'image name is missing'],500);
@@ -232,6 +238,12 @@ class MediaApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to delete content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete files.'), 403);
+		}
+
 		if(!isset($this->params['name']))
 		if(!isset($this->params['name']))
 		{
 		{
 			return $response->withJson(['errors' => 'file name is missing'],500);	
 			return $response->withJson(['errors' => 'file name is missing'],500);	

+ 16 - 0
system/Controllers/MetaApiController.php

@@ -134,6 +134,12 @@ class MetaApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri()->withUserInfo('');
 		$this->uri 		= $request->getUri()->withUserInfo('');
 
 
+		# minimum permission is that user is allowed to update his own content
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
+		{
+			return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to update content.']), 403);
+		}
+
 		$tab 			= isset($this->params['tab']) ? $this->params['tab'] : false;
 		$tab 			= isset($this->params['tab']) ? $this->params['tab'] : false;
 		$metaInput		= isset($this->params['data']) ? $this->params['data'] : false ;
 		$metaInput		= isset($this->params['data']) ? $this->params['data'] : false ;
 		$objectName		= 'meta';
 		$objectName		= 'meta';
@@ -150,6 +156,16 @@ class MetaApiController extends ContentController
 		# set item 
 		# set item 
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 
 
+		# if user has no right to delete content from others (eg admin or editor)
+		if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
+		{
+			# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
+			if(!$this->checkContentOwnership())
+			{
+				return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to edit content.']), 403);
+			}
+		}
+
 		# if item is a folder
 		# if item is a folder
 		if($this->item->elementType == "folder")
 		if($this->item->elementType == "folder")
 		{
 		{

+ 304 - 275
system/Controllers/SettingsController.php

@@ -9,6 +9,8 @@ use Typemill\Models\Validation;
 use Typemill\Models\User;
 use Typemill\Models\User;
 use Typemill\Models\ProcessFile;
 use Typemill\Models\ProcessFile;
 use Typemill\Models\ProcessImage;
 use Typemill\Models\ProcessImage;
+use Typemill\Events\OnUserfieldsLoaded;
+use Typemill\Events\OnSystemnaviLoaded;
 
 
 class SettingsController extends Controller
 class SettingsController extends Controller
 {	
 {	
@@ -26,26 +28,28 @@ class SettingsController extends Controller
 		$locale				= isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2) : 'en';
 		$locale				= isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2) : 'en';
 		$users				= $user->getUsers();
 		$users				= $user->getUsers();
 		$route 				= $request->getAttribute('route');
 		$route 				= $request->getAttribute('route');
-
-		return $this->render($response, 'settings/system.twig', array('settings' => $settings, 'copyright' => $copyright, 'languages' => $languages, 'locale' => $locale, 'formats' => $defaultSettings['formats'] ,'users' => $users, 'route' => $route->getName() ));
+		$navigation 		= $this->getNavigation();
+
+		# set navigation active
+		$navigation['System']['active'] = true;
+
+		return $this->render($response, 'settings/system.twig', array(
+			'settings' 		=> $settings,
+			'acl' 			=> $this->c->acl, 
+			'navigation'	=> $navigation,
+			'copyright' 	=> $copyright, 
+			'languages' 	=> $languages, 
+			'locale' 		=> $locale, 
+			'formats' 		=> $defaultSettings['formats'],
+			'users' 		=> $users, 
+			'route' 		=> $route->getName() 
+		));
 	}
 	}
 	
 	
 	public function saveSettings($request, $response, $args)
 	public function saveSettings($request, $response, $args)
 	{
 	{
 		if($request->isPost())
 		if($request->isPost())
-		{
-			$referer		= $request->getHeader('HTTP_REFERER');
-			$uri 			= $request->getUri()->withUserInfo('');
-			$base_url		= $uri->getBaseUrl();
-
-			/* security, users should not be able to fake post with settings from other typemill pages.
-			if(!isset($referer[0]) OR $referer[0] !== $base_url . '/tm/settings' )
-			{
-				$this->c->flash->addMessage('error', 'illegal referer');
-				return $response->withRedirect($this->c->router->pathFor('settings.show'));				
-			}
-			*/
-			
+		{			
 			$settings 			= \Typemill\Settings::getUserSettings();
 			$settings 			= \Typemill\Settings::getUserSettings();
 			$defaultSettings	= \Typemill\Settings::getDefaultSettings();
 			$defaultSettings	= \Typemill\Settings::getDefaultSettings();
 			$params 			= $request->getParams();
 			$params 			= $request->getParams();
@@ -232,9 +236,19 @@ class SettingsController extends Controller
 		}
 		}
 		
 		
 		/* add the users for navigation */
 		/* add the users for navigation */
-		$route 		= $request->getAttribute('route');
-
-		return $this->render($response, 'settings/themes.twig', array('settings' => $userSettings, 'themes' => $themedata, 'route' => $route->getName() ));
+		$route 	= $request->getAttribute('route');
+		$navigation = $this->getNavigation();
+
+		# set navigation active
+		$navigation['Themes']['active'] = true;
+
+		return $this->render($response, 'settings/themes.twig', array(
+			'settings' 		=> $userSettings,
+			'acl' 			=> $this->c->acl,
+			'navigation' 	=> $navigation, 
+			'themes' 		=> $themedata, 
+			'route' 		=> $route->getName() 
+		));
 	}
 	}
 	
 	
 	public function showPlugins($request, $response, $args)
 	public function showPlugins($request, $response, $args)
@@ -300,8 +314,18 @@ class SettingsController extends Controller
 		}
 		}
 		
 		
 		$route 	= $request->getAttribute('route');
 		$route 	= $request->getAttribute('route');
+		$navigation = $this->getNavigation();
+
+		# set navigation active
+		$navigation['Plugins']['active'] = true;
 		
 		
-		return $this->render($response, 'settings/plugins.twig', array('settings' => $userSettings, 'plugins' => $plugins, 'route' => $route->getName() ));
+		return $this->render($response, 'settings/plugins.twig', array(
+			'settings' 		=> $userSettings,
+			'acl' 			=> $this->c->acl,
+			'navigation' 	=> $navigation,
+			'plugins' 		=> $plugins,
+			'route' 		=> $route->getName() 
+		));
 	}
 	}
 
 
 	/*************************************
 	/*************************************
@@ -311,19 +335,7 @@ class SettingsController extends Controller
 	public function saveThemes($request, $response, $args)
 	public function saveThemes($request, $response, $args)
 	{
 	{
 		if($request->isPost())
 		if($request->isPost())
-		{
-			$referer		= $request->getHeader('HTTP_REFERER');
-			$uri 			= $request->getUri()->withUserInfo('');
-			$base_url		= $uri->getBaseUrl();
-
-			/* users should not be able to fake post with settings from other typemill pages.
-			if(!isset($referer[0]) OR $referer[0] !== $base_url . '/tm/themes' )
-			{
-				$this->c->flash->addMessage('error', 'illegal referer');
-				return $response->withRedirect($this->c->router->pathFor('themes.show'));
-			}
-			*/
-	
+		{	
 			$userSettings 	= \Typemill\Settings::getUserSettings();
 			$userSettings 	= \Typemill\Settings::getUserSettings();
 			$params 		= $request->getParams();
 			$params 		= $request->getParams();
 			$themeName		= isset($params['theme']) ? $params['theme'] : false;
 			$themeName		= isset($params['theme']) ? $params['theme'] : false;
@@ -410,18 +422,6 @@ class SettingsController extends Controller
 	{
 	{
 		if($request->isPost())
 		if($request->isPost())
 		{
 		{
-			$referer		= $request->getHeader('HTTP_REFERER');
-			$uri 			= $request->getUri()->withUserInfo('');
-			$base_url		= $uri->getBaseUrl();
-
-			/* security, users should not be able to fake post with settings from other typemill pages.
-			if(!isset($referer[0]) OR $referer[0] !== $base_url . '/tm/plugins' )
-			{
-				$this->c->flash->addMessage('error', 'illegal referer');
-				return $response->withRedirect($this->c->router->pathFor('plugins.show'));
-			}
-			*/
-
 			$userSettings 	= \Typemill\Settings::getUserSettings();
 			$userSettings 	= \Typemill\Settings::getUserSettings();
 			$pluginSettings	= array();
 			$pluginSettings	= array();
 			$userInput 		= $request->getParams();
 			$userInput 		= $request->getParams();
@@ -481,121 +481,12 @@ class SettingsController extends Controller
 		}
 		}
 	}
 	}
 
 
-	private function validateInput($objectType, $objectName, $userInput, $validate, $originalSettings = NULL)
-	{
-		if(!$originalSettings)
-		{
-			# fetch the original settings from the folder (plugin or theme) to get the field definitions
-			$originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName);
-		}
-
-		# images get special treatment
-		$imageFieldDefinitions = array();
-
-		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;
-				}
-			}
-			
-			# if the plugin defines frontend fields
-			if(isset($originalSettings['public']))
-			{
-				$originalFields['recaptcha'] = ['type' => 'checkbox', 'label' => 'Google Recaptcha', 'checkboxlabel' => 'Activate Recaptcha' ];
-				$originalFields['recaptcha_webkey'] = ['type' => 'text', 'label' => 'Recaptcha Website Key', 'help' => 'Add the recaptcha website key here. You can get the key from the recaptcha website.', 'description' => 'The website key is mandatory if you activate the recaptcha field'];
-				$originalFields['recaptcha_secretkey'] = ['type' => 'text', 'label' => 'Recaptcha Secret Key', 'help' => 'Add the recaptcha secret key here. You can get the key from the recaptcha website.', 'description' => 'The secret key is mandatory if you activate the recaptcha field'];
-			}
-
-			# if plugin is not active, then skip required
-			$skiprequired = false;
-			if($objectType == 'plugins' && !isset($userInput['active']))
-			{
-				$skiprequired = true;
-			}
-			
-			/* 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($originalFields[$fieldName]) ? $originalFields[$fieldName] : false;
-
-				if($fieldDefinition)
-				{
-					/* validate user input for this field */
-					$validate->objectField($fieldName, $fieldValue, $objectName, $fieldDefinition, $skiprequired);
-					
-					if($fieldDefinition['type'] == 'image')
-					{
-						# we want to return all images-fields for further processing
-						$imageFieldDefinitions[$fieldName] = $fieldDefinition;
-					}
-				}
-				if(!$fieldDefinition && $fieldName != 'active')
-				{
-					$_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
-				}
-			}
-		}
-
-		return $imageFieldDefinitions;
-	}
-
-	protected function saveImages($imageFields, $userInput, $userSettings, $files)
-	{
-
-		# initiate image processor with standard image sizes
-		$processImages = new ProcessImage($userSettings['images']);
-
-		if(!$processImages->checkFolders('images'))
-		{
-			$this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.');
-			return false; 
-		}
-
-		foreach($imageFields as $fieldName => $imageField)
-		{
-			if(isset($userInput[$fieldName]))
-			{
-				# handle single input with single file upload
-    			$image = $files[$fieldName];
-    		
-    			if($image->getError() === UPLOAD_ERR_OK) 
-    			{
-    				# not the most elegant, but createImage expects a base64-encoded string.
-    				$imageContent = $image->getStream()->getContents();
-					$imageData = base64_encode($imageContent);
-					$imageSrc = 'data: ' . $image->getClientMediaType() . ';base64,' . $imageData;
-
-					if($processImages->createImage($imageSrc, $image->getClientFilename(), $userSettings['images'], $overwrite = NULL))
-					{
-						# returns image path to media library
-						$userInput[$fieldName] = $processImages->publishImage();
-					}
-			    }
-			}
-		}
-		return $userInput;
-	}
-
 	/***********************
 	/***********************
 	**   USER MANAGEMENT  **
 	**   USER MANAGEMENT  **
 	***********************/
 	***********************/
 
 
 	public function showAccount($request, $response, $args)
 	public function showAccount($request, $response, $args)
-	{		
+	{
 		$username 	= $_SESSION['user'];
 		$username 	= $_SESSION['user'];
 
 
 		$validate 	= new Validation();
 		$validate 	= new Validation();
@@ -613,27 +504,31 @@ class SettingsController extends Controller
 			$fieldsModel	= new Fields();
 			$fieldsModel	= new Fields();
 
 
 			# get the field-definitions
 			# get the field-definitions
-			$fieldDefinitions = $this->getUserFields($_SESSION['role']);
+			$fieldDefinitions = $this->getUserFields($userdata['userrole']);
 
 
 			# prepare userdata for field-builder
 			# prepare userdata for field-builder
-			$userSettings['user'][$username] = $userdata;
+			$userSettings['users']['user'] = $userdata;
 
 
 			# generate the input form
 			# generate the input form
-			$userform = $fieldsModel->getFields($userSettings, 'user', $username, $fieldDefinitions);
+			$userform = $fieldsModel->getFields($userSettings, 'users', 'user', $fieldDefinitions);
 
 
 			$route = $request->getAttribute('route');
 			$route = $request->getAttribute('route');
+			$navigation = $this->getNavigation();
+
+			# set navigation active
+			$navigation['Account']['active'] = true;
 
 
 			return $this->render($response, 'settings/user.twig', array(
 			return $this->render($response, 'settings/user.twig', array(
-				'settings' 		=> $settings, 
+				'settings' 		=> $settings,
+				'acl' 			=> $this->c->acl,
+				'navigation'	=> $navigation, 
 				'usersettings' 	=> $userSettings, 		// needed for image url in form, will overwrite settings for field-template
 				'usersettings' 	=> $userSettings, 		// needed for image url in form, will overwrite settings for field-template
 				'userform' 		=> $userform, 			// field model, needed to generate frontend-field
 				'userform' 		=> $userform, 			// field model, needed to generate frontend-field
 				'userdata' 		=> $userdata, 			// needed to fill form with data
 				'userdata' 		=> $userdata, 			// needed to fill form with data
 #				'userrole' 		=> false,				// not needed ? 
 #				'userrole' 		=> false,				// not needed ? 
 #				'username' 		=> $args['username'], 	// not needed ?
 #				'username' 		=> $args['username'], 	// not needed ?
 				'route' 		=> $route->getName()  	// needed to set link active
 				'route' 		=> $route->getName()  	// needed to set link active
-			));
-			
-			return $this->render($response, 'settings/user.twig', array('settings' => $settings, 'usersettings' => $userSettings, 'userdata' => $userdata, 'userrole' => false, 'username' => $username, 'userform' => $userform, 'route' => $route->getName() ));
+			));			
 		}
 		}
 		
 		
 		$this->c->flash->addMessage('error', 'User does not exists');
 		$this->c->flash->addMessage('error', 'User does not exists');
@@ -642,7 +537,7 @@ class SettingsController extends Controller
 	
 	
 	public function showUser($request, $response, $args)
 	public function showUser($request, $response, $args)
 	{
 	{
-		# if user has no rights to watch userlist, then only show his user-entry
+		# if user has no rights to watch userlist, then redirect to 
 		if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view') && $_SESSION['user'] !== $args['username'] )
 		if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view') && $_SESSION['user'] !== $args['username'] )
 		{
 		{
 			return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] ));
 			return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] ));
@@ -655,7 +550,7 @@ class SettingsController extends Controller
 			# get settings
 			# get settings
 			$settings 	= $this->c->get('settings');
 			$settings 	= $this->c->get('settings');
 
 
-			# get user with userdata		
+			# get user with userdata
 			$user 		= new User();
 			$user 		= new User();
 			$userdata 	= $user->getSecureUser($args['username']);
 			$userdata 	= $user->getSecureUser($args['username']);
 
 
@@ -668,15 +563,21 @@ class SettingsController extends Controller
 			$fieldDefinitions = $this->getUserFields($userdata['userrole']);
 			$fieldDefinitions = $this->getUserFields($userdata['userrole']);
 
 
 			# prepare userdata for field-builder
 			# prepare userdata for field-builder
-			$userSettings['user'][$username] = $userdata;
+			$userSettings['users']['user'] = $userdata;
 
 
 			# generate the input form
 			# generate the input form
-			$userform = $fieldsModel->getFields($userSettings, 'user', $username, $fieldDefinitions);
+			$userform = $fieldsModel->getFields($userSettings, 'users', 'user', $fieldDefinitions);
 
 
 			$route = $request->getAttribute('route');
 			$route = $request->getAttribute('route');
+			$navigation = $this->getNavigation();
+
+			# set navigation active
+			$navigation['Users']['active'] = true;
 			
 			
 			return $this->render($response, 'settings/user.twig', array(
 			return $this->render($response, 'settings/user.twig', array(
-				'settings' 		=> $settings, 
+				'settings' 		=> $settings,
+				'acl' 			=> $this->c->acl, 
+				'navigation'	=> $navigation, 
 				'usersettings' 	=> $userSettings, 		// needed for image url in form, will overwrite settings for field-template
 				'usersettings' 	=> $userSettings, 		// needed for image url in form, will overwrite settings for field-template
 				'userform' 		=> $userform, 			// field model, needed to generate frontend-field
 				'userform' 		=> $userform, 			// field model, needed to generate frontend-field
 				'userdata' 		=> $userdata, 			// needed to fill form with data
 				'userdata' 		=> $userdata, 			// needed to fill form with data
@@ -690,84 +591,59 @@ class SettingsController extends Controller
 		return $response->withRedirect($this->c->router->pathFor('user.account'));
 		return $response->withRedirect($this->c->router->pathFor('user.account'));
 	}
 	}
 
 
-	private function getUserFields($role)
-	{
-		$fields = [];
-		$fields['username'] 	= ['label' => 'Username (read only)', 'type' => 'text', 'readonly' => true];
-		$fields['image'] 		= ['label' => 'Profile-Image', 'type' => 'image'];
-		$fields['description'] 	= ['label' => 'Author-Description (Markdown)', 'type' => 'textarea'];
-		$fields['firstname'] 	= ['label' => 'First Name', 'type' => 'text'];
-		$fields['lastname'] 	= ['label' => 'Last Name', 'type' => 'text'];
-		$fields['email'] 		= ['label' => 'E-Mail', 'type' => 'text', 'required' => true];
-		$fields['userrole'] 	= ['label' => 'Role', 'type' => 'text', 'readonly' => true];
-		$fields['password'] 	= ['label' => 'Actual Password', 'type' => 'password'];
-		$fields['newpassword'] 	= ['label' => 'New Password', 'type' => 'password'];
-
-		# dispatch fields;
-
-		# change admin stuff
-		if($_SESSION['role'] == 'administrator')
-		{
-			$userroles = $this->c->acl->getRoles();
-			$options = [];
-
-			# we need associative array to make select-field with key/value work
-			foreach($userroles as $userrole)
-			{
-				$options[$userrole] = $userrole;
- 			}
-
-			$fields['userrole'] = ['label' => 'Role', 'type' => 'select', 'options' => $options];
-		}
-
-		$userform = [];
-		$userform['forms']['fields'] = $fields;
-		return $userform; 
-	}
-
 	public function listUser($request, $response)
 	public function listUser($request, $response)
 	{
 	{
-		$user		= new User();
-		$users		= $user->getUsers();
-		$userdata 	= array();
-		$route 		= $request->getAttribute('route');
-		$settings 	= $this->c->get('settings');
+		$user			= new User();
+		$users			= $user->getUsers();
+		$userdata 		= array();
+		$route 			= $request->getAttribute('route');
+		$settings 		= $this->c->get('settings');
+		$navigation 	= $this->getNavigation();
 		
 		
+		# set navigation active
+		$navigation['Users']['active'] = true;
+
 		foreach($users as $username)
 		foreach($users as $username)
 		{
 		{
 			$userdata[] = $user->getUser($username);
 			$userdata[] = $user->getUser($username);
 		}
 		}
 		
 		
-		return $this->render($response, 'settings/userlist.twig', array('settings' => $settings, 'users' => $users, 'userdata' => $userdata, 'route' => $route->getName() ));
+		return $this->render($response, 'settings/userlist.twig', array(
+			'settings' 		=> $settings,
+			'acl' 			=> $this->c->acl, 
+			'navigation' 	=> $navigation, 
+			'users' 		=> $users, 
+			'userdata' 		=> $userdata, 
+			'route' 		=> $route->getName() 
+		));
 	}
 	}
 	
 	
 	public function newUser($request, $response, $args)
 	public function newUser($request, $response, $args)
 	{
 	{
-		$user 		= new User();
-		$users		= $user->getUsers();
-		$userrole	= $user->getUserroles();
-		$route 		= $request->getAttribute('route');
-		$settings 	= $this->c->get('settings');
-
-		return $this->render($response, 'settings/usernew.twig', array('settings' => $settings, 'users' => $users, 'userrole' => $userrole, 'route' => $route->getName() ));
+		$user 			= new User();
+		$users			= $user->getUsers();
+		$userroles 		= $this->c->acl->getRoles();
+		$route 			= $request->getAttribute('route');
+		$settings 		= $this->c->get('settings');
+		$navigation 	= $this->getNavigation();
+
+		# set navigation active
+		$navigation['Users']['active'] = true;
+
+		return $this->render($response, 'settings/usernew.twig', array(
+			'settings' 		=> $settings, 
+			'acl' 			=> $this->c->acl, 
+			'navigation'	=> $navigation,
+			'users' 		=> $users, 
+			'userrole' 		=> $userroles, 
+			'route' 		=> $route->getName() 
+		));
 	}
 	}
 		
 		
 	public function createUser($request, $response, $args)
 	public function createUser($request, $response, $args)
 	{
 	{
 		if($request->isPost())
 		if($request->isPost())
 		{
 		{
-			$referer		= $request->getHeader('HTTP_REFERER');
-			$uri 			= $request->getUri()->withUserInfo('');
-			$base_url		= $uri->getBaseUrl();
-
-			/* security, users should not be able to fake post with settings from other typemill pages.
-			if(!isset($referer[0]) OR $referer[0] !== $base_url . '/tm/user/new' )
-			{
-				$this->c->flash->addMessage('error', 'illegal referer');
-				return $response->withRedirect($this->c->router->pathFor('user.new'));
-			}
-			*/
-			
 			$params 		= $request->getParams();
 			$params 		= $request->getParams();
 			$user 			= new User();
 			$user 			= new User();
 			$validate		= new Validation();
 			$validate		= new Validation();
@@ -776,12 +652,10 @@ class SettingsController extends Controller
 			if($validate->newUser($params, $userroles))
 			if($validate->newUser($params, $userroles))
 			{
 			{
 				$userdata	= array(
 				$userdata	= array(
-					'username' => $params['username'], 
-					'firstname' => $params['firstname'], 
-					'lastname' => $params['lastname'], 
-					'email' => $params['email'], 
-					'userrole' => $params['userrole'], 
-					'password' => $params['password']);
+					'username' 		=> $params['username'], 
+					'email' 		=> $params['email'], 
+					'userrole' 		=> $params['userrole'], 
+					'password' 		=> $params['password']);
 				
 				
 				$user->createUser($userdata);
 				$user->createUser($userdata);
 
 
@@ -799,29 +673,16 @@ class SettingsController extends Controller
 
 
 		if($request->isPost())
 		if($request->isPost())
 		{
 		{
-			$referer		= $request->getHeader('HTTP_REFERER');
-			$uri 			= $request->getUri()->withUserInfo('');
-			$base_url		= $uri->getBaseUrl();
-
-			/* security, users should not be able to fake post with settings from other typemill pages.
-			if(!isset($referer[0]) OR strpos($referer[0], $base_url . '/tm/user/') === false )
-			{
-				$this->c->flash->addMessage('error', 'illegal referer');
-				return $response->withRedirect($this->c->router->pathFor('user.list'));
-			}
-			*/
-			
 			$params 		= $request->getParams();
 			$params 		= $request->getParams();
 			$userdata 		= $params['user'];
 			$userdata 		= $params['user'];
 			$user 			= new User();
 			$user 			= new User();
 			$validate		= new Validation();
 			$validate		= new Validation();
 			$userroles 		= $this->c->acl->getRoles();
 			$userroles 		= $this->c->acl->getRoles();
+			
+			$redirectRoute	= ($userdata['username'] == $_SESSION['user']) ? $this->c->router->pathFor('user.account') : $this->c->router->pathFor('user.show', ['username' => $userdata['username']]);
 
 
-			print_r($params);
-			die();
-						
 			# check if user is allowed to view (edit) userlist and other users
 			# check if user is allowed to view (edit) userlist and other users
-			if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view'))
+			if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
 			{
 			{
 				# if an editor tries to update other userdata than its own */
 				# if an editor tries to update other userdata than its own */
 				if($_SESSION['user'] !== $userdata['username'])
 				if($_SESSION['user'] !== $userdata['username'])
@@ -829,7 +690,7 @@ class SettingsController extends Controller
 					return $response->withRedirect($this->c->router->pathFor('user.account'));
 					return $response->withRedirect($this->c->router->pathFor('user.account'));
 				}
 				}
 				
 				
-				# non admins cannot change his userrole, so set it to session-value
+				# non admins cannot change their userrole, so set it to session-value
 				$userdata['userrole'] = $_SESSION['role'];
 				$userdata['userrole'] = $_SESSION['role'];
 			}
 			}
 
 
@@ -840,7 +701,7 @@ class SettingsController extends Controller
 				$userfields = $this->getUserFields($userdata['userrole']);
 				$userfields = $this->getUserFields($userdata['userrole']);
 				$imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields);
 				$imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields);
 
 
-				if(!isset($_SESSION['errors']) && !empty($imageFields))
+				if(!empty($imageFields))
 				{
 				{
 					$images = $request->getUploadedFiles();
 					$images = $request->getUploadedFiles();
 
 
@@ -848,23 +709,36 @@ class SettingsController extends Controller
 					{
 					{
 						# set image size
 						# set image size
 						$settings = $this->c->get('settings');
 						$settings = $this->c->get('settings');
-						$settings['images']['live'] = ['width' => 500, 'height' => 500];
-						$userdata = $this->saveImages($imageFields, $userdata, $settings, $images['user']);
+						$settings->replace(['images' => ['live' => ['width' => 500, 'height' => 500]]]);
+						$imageresult = $this->saveImages($imageFields, $userdata, $settings, $images['user']);
+		
+						if(isset($_SESSION['slimFlash']['error']))
+						{
+							return $response->withRedirect($redirectRoute);
+						}
+						elseif(isset($imageresult['username']))
+						{
+							$userdata = $imageresult;
+						}
 					}
 					}
 				}
 				}
-			
+
 				# check for errors and redirect to path, if errors found */
 				# check for errors and redirect to path, if errors found */
 				if(isset($_SESSION['errors']))
 				if(isset($_SESSION['errors']))
 				{
 				{
 					$this->c->flash->addMessage('error', 'Please correct the errors');
 					$this->c->flash->addMessage('error', 'Please correct the errors');
-					return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']]));
+					return $response->withRedirect($redirectRoute);
 				}
 				}
 
 
 				if(empty($userdata['password']) AND empty($userdata['newpassword']))
 				if(empty($userdata['password']) AND empty($userdata['newpassword']))
 				{
 				{
+					# make sure no invalid passwords go into model
+					unset($userdata['password']);
+					unset($userdata['newpassword']);
+
 					$user->updateUser($userdata);
 					$user->updateUser($userdata);
 					$this->c->flash->addMessage('info', 'Saved all changes');
 					$this->c->flash->addMessage('info', 'Saved all changes');
-					return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']]));
+					return $response->withRedirect($redirectRoute);
 				}
 				}
 				elseif($validate->newPassword($userdata))
 				elseif($validate->newPassword($userdata))
 				{
 				{
@@ -873,42 +747,30 @@ class SettingsController extends Controller
 
 
 					$user->updateUser($userdata);
 					$user->updateUser($userdata);
 					$this->c->flash->addMessage('info', 'Saved all changes');
 					$this->c->flash->addMessage('info', 'Saved all changes');
-					return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']]));
+					return $response->withRedirect($redirectRoute);
 				}
 				}
 			}
 			}
 			
 			
 			$this->c->flash->addMessage('error', 'Please correct your input');
 			$this->c->flash->addMessage('error', 'Please correct your input');
-			return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']]));
+			return $response->withRedirect($redirectRoute);
 		}
 		}
 	}
 	}
 	
 	
 	public function deleteUser($request, $response, $args)
 	public function deleteUser($request, $response, $args)
 	{
 	{
 		if($request->isPost())
 		if($request->isPost())
-		{
-			$referer		= $request->getHeader('HTTP_REFERER');
-			$uri 			= $request->getUri()->withUserInfo('');
-			$base_url		= $uri->getBaseUrl();
-
-			/* security, users should not be able to fake post with settings from other typemill pages.
-			if(!isset($referer[0]) OR strpos($referer[0], $base_url . '/tm/user/') === false )
-			{
-				$this->c->flash->addMessage('error', 'illegal referer');
-				return $response->withRedirect($this->c->router->pathFor('user.list'));
-			}
-			*/
-			
+		{			
 			$params 		= $request->getParams();
 			$params 		= $request->getParams();
 			$validate		= new Validation();
 			$validate		= new Validation();
 			$user			= new User();
 			$user			= new User();
 
 
-			/* non admins have different update rights */
-			if($_SESSION['role'] !== 'administrator')
+			# check if user is allowed to view (edit) userlist and other users
+			if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
 			{
 			{
-				/* if an editor tries to delete other user than its own */
+				# if an editor tries to delete other user than its own
 				if($_SESSION['user'] !== $params['username'])
 				if($_SESSION['user'] !== $params['username'])
 				{
 				{
-					return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] ));
+					return $response->withRedirect($this->c->router->pathFor('user.account'));
 				}				
 				}				
 			}
 			}
 			
 			
@@ -924,7 +786,7 @@ class SettingsController extends Controller
 				}
 				}
 				
 				
 				$this->c->flash->addMessage('info', 'Say goodbye, the user is gone!');
 				$this->c->flash->addMessage('info', 'Say goodbye, the user is gone!');
-				return $response->withRedirect($this->c->router->pathFor('user.list'));			
+				return $response->withRedirect($this->c->router->pathFor('user.list'));
 			}
 			}
 			
 			
 			$this->c->flash->addMessage('error', 'Ups, we did not find that user');
 			$this->c->flash->addMessage('error', 'Ups, we did not find that user');
@@ -932,6 +794,48 @@ class SettingsController extends Controller
 		}
 		}
 	}
 	}
 
 
+	private function getUserFields($role)
+	{
+		$fields = [];
+		$fields['username'] 	= ['label' => 'Username (read only)', 'type' => 'text', 'readonly' => true];
+		$fields['firstname'] 	= ['label' => 'First Name', 'type' => 'text'];
+		$fields['lastname'] 	= ['label' => 'Last Name', 'type' => 'text'];
+		$fields['email'] 		= ['label' => 'E-Mail', 'type' => 'text', 'required' => true];
+		$fields['userrole'] 	= ['label' => 'Role', 'type' => 'text', 'readonly' => true];
+		$fields['password'] 	= ['label' => 'Actual Password', 'type' => 'password'];
+		$fields['newpassword'] 	= ['label' => 'New Password', 'type' => 'password'];
+
+		# dispatch fields;
+		$fields = $this->c->dispatcher->dispatch('onUserfieldsLoaded', new OnUserfieldsLoaded($fields))->getData();
+
+		# only roles who can edit content need profile image and description
+		if($this->c->acl->isAllowed($role, 'content', 'create'))
+		{
+			$newfield['image'] 			= ['label' => 'Profile-Image', 'type' => 'image'];
+			$newfield['description'] 	= ['label' => 'Author-Description (Markdown)', 'type' => 'textarea'];			
+			array_splice($fields,1,0,$newfield);
+		}
+
+		# Only admin can change userroles
+		if($this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
+		{
+			$userroles = $this->c->acl->getRoles();
+			$options = [];
+
+			# we need associative array to make select-field with key/value work
+			foreach($userroles as $userrole)
+			{
+				$options[$userrole] = $userrole;
+ 			}
+
+			$fields['userrole'] = ['label' => 'Role', 'type' => 'select', 'options' => $options];
+		}
+
+		$userform = [];
+		$userform['forms']['fields'] = $fields;
+		return $userform;
+	}
+
 	private function getThemes()
 	private function getThemes()
 	{
 	{
 		$themeFolder 	= $this->c->get('settings')['rootPath'] . $this->c->get('settings')['themeFolder'];
 		$themeFolder 	= $this->c->get('settings')['rootPath'] . $this->c->get('settings')['themeFolder'];
@@ -975,4 +879,129 @@ class SettingsController extends Controller
 			'fr' => 'French',
 			'fr' => 'French',
 		);
 		);
 	}
 	}
+
+	private function getNavigation()
+	{
+		$navigation = [
+			'System'	=> ['routename' => 'settings.show', 'icon' => 'icon-wrench', 'aclresource' => 'system', 'aclprivilege' => 'view'],
+			'Themes'	=> ['routename' => 'themes.show', 'icon' => 'icon-paint-brush', 'aclresource' => 'system', 'aclprivilege' => 'view'],
+			'Plugins'	=> ['routename' => 'plugins.show', 'icon' => 'icon-plug', 'aclresource' => 'system', 'aclprivilege' => 'view'],
+			'Account'	=> ['routename' => 'user.account', 'icon' => 'icon-user', 'aclresource' => 'user', 'aclprivilege' => 'view'],
+			'Users'		=> ['routename' => 'user.list', 'icon' => 'icon-group', 'aclresource' => 'userlist', 'aclprivilege' => 'view']
+		];
+
+		# dispatch fields;
+		$navigation = $this->c->dispatcher->dispatch('onSystemnaviLoaded', new OnSystemnaviLoaded($navigation))->getData();
+
+		return $navigation;
+	}
+
+	private function validateInput($objectType, $objectName, $userInput, $validate, $originalSettings = NULL)
+	{
+		if(!$originalSettings)
+		{
+			# fetch the original settings from the folder (plugin or theme) to get the field definitions
+			$originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName);
+		}
+
+		# images get special treatment
+		$imageFieldDefinitions = array();
+
+		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;
+				}
+			}
+			
+			# if the plugin defines frontend fields
+			if(isset($originalSettings['public']))
+			{
+				$originalFields['recaptcha'] = ['type' => 'checkbox', 'label' => 'Google Recaptcha', 'checkboxlabel' => 'Activate Recaptcha' ];
+				$originalFields['recaptcha_webkey'] = ['type' => 'text', 'label' => 'Recaptcha Website Key', 'help' => 'Add the recaptcha website key here. You can get the key from the recaptcha website.', 'description' => 'The website key is mandatory if you activate the recaptcha field'];
+				$originalFields['recaptcha_secretkey'] = ['type' => 'text', 'label' => 'Recaptcha Secret Key', 'help' => 'Add the recaptcha secret key here. You can get the key from the recaptcha website.', 'description' => 'The secret key is mandatory if you activate the recaptcha field'];
+			}
+
+			# if plugin is not active, then skip required
+			$skiprequired = false;
+			if($objectType == 'plugins' && !isset($userInput['active']))
+			{
+				$skiprequired = true;
+			}
+			
+			/* 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($originalFields[$fieldName]) ? $originalFields[$fieldName] : false;
+
+				if($fieldDefinition)
+				{
+					/* validate user input for this field */
+					$validate->objectField($fieldName, $fieldValue, $objectName, $fieldDefinition, $skiprequired);
+					
+					if($fieldDefinition['type'] == 'image')
+					{
+						# we want to return all images-fields for further processing
+						$imageFieldDefinitions[$fieldName] = $fieldDefinition;
+					}
+				}
+				if(!$fieldDefinition && $objectType != 'users' && $fieldName != 'active')
+				{
+					$_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
+				}
+			}
+		}
+
+		return $imageFieldDefinitions;
+	}
+
+	protected function saveImages($imageFields, $userInput, $userSettings, $files)
+	{
+
+		# initiate image processor with standard image sizes
+		$processImages = new ProcessImage($userSettings['images']);
+
+		if(!$processImages->checkFolders('images'))
+		{
+			$this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.');
+			return false; 
+		}
+
+		foreach($imageFields as $fieldName => $imageField)
+		{
+			if(isset($userInput[$fieldName]))
+			{
+				# handle single input with single file upload
+    			$image = $files[$fieldName];
+    		
+    			if($image->getError() === UPLOAD_ERR_OK) 
+    			{
+    				# not the most elegant, but createImage expects a base64-encoded string.
+    				$imageContent = $image->getStream()->getContents();
+					$imageData = base64_encode($imageContent);
+					$imageSrc = 'data: ' . $image->getClientMediaType() . ';base64,' . $imageData;
+
+					if($processImages->createImage($imageSrc, $image->getClientFilename(), $userSettings['images'], $overwrite = NULL))
+					{
+						# returns image path to media library
+						$userInput[$fieldName] = $processImages->publishImage();
+					}
+			    }
+			}
+		}
+		return $userInput;
+	}	
 }
 }

+ 3 - 3
system/Controllers/SetupController.php

@@ -66,11 +66,11 @@ class SetupController extends Controller
 			$validate		= new Validation();
 			$validate		= new Validation();
 			$user			= new User();
 			$user			= new User();
 
 
-			/* set user as admin */
+			# set user as admin
 			$params['userrole'] = 'administrator';
 			$params['userrole'] = 'administrator';
 			
 			
-			/* get userroles for validation */
-			$userroles		= $user->getUserroles();
+			# get userroles for validation
+			$userroles 		= $this->c->acl->getRoles();
 			
 			
 			/* validate user */
 			/* validate user */
 			if($validate->newUser($params, $userroles))
 			if($validate->newUser($params, $userroles))

+ 14 - 0
system/Events/OnSystemnaviLoaded.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Typemill\Events;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event for settings
+ */
+ 
+class OnSystemnaviLoaded extends BaseEvent
+{
+
+}

+ 14 - 0
system/Events/OnUserfieldsLoaded.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Typemill\Events;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event for the page tree.
+ */
+ 
+class OnUserfieldsLoaded extends BaseEvent
+{
+
+}

+ 10 - 0
system/Extensions/TwigUserExtension.php

@@ -8,6 +8,7 @@ class TwigUserExtension extends \Twig_Extension
 	{
 	{
 		return [
 		return [
 			new \Twig_SimpleFunction('is_role', array($this, 'isRole' )),
 			new \Twig_SimpleFunction('is_role', array($this, 'isRole' )),
+			new \Twig_SimpleFunction('get_role', array($this, 'getRole' )),
 			new \Twig_SimpleFunction('get_username', array($this, 'getUsername' ))
 			new \Twig_SimpleFunction('get_username', array($this, 'getUsername' ))
 		];
 		];
 	}
 	}
@@ -21,6 +22,15 @@ class TwigUserExtension extends \Twig_Extension
 		
 		
 		return false;
 		return false;
 	}
 	}
+
+	public function getRole()
+	{
+		if(isset($_SESSION['role']))
+		{
+			return $_SESSION['role'];
+		}
+		return false;
+	}
 	
 	
 	public function getUsername()
 	public function getUsername()
 	{
 	{

+ 3 - 0
system/Middleware/RedirectIfNoAdmin.php

@@ -8,6 +8,8 @@ use Slim\Http\Response;
 
 
 class RedirectIfNoAdmin
 class RedirectIfNoAdmin
 {	
 {	
+	# NOT IN USE ANYMORE
+	/*
 	protected $router;
 	protected $router;
 	
 	
 	public function __construct(RouterInterface $router, $flash)
 	public function __construct(RouterInterface $router, $flash)
@@ -29,4 +31,5 @@ class RedirectIfNoAdmin
 		
 		
 		return $next($request, $response);
 		return $next($request, $response);
 	}
 	}
+	*/
 }
 }

+ 22 - 10
system/Models/User.php

@@ -66,25 +66,37 @@ class User extends WriteYaml
 	{
 	{
 		$userdata = $this->getUser($params['username']);
 		$userdata = $this->getUser($params['username']);
 		
 		
+		# make sure passwords are not overwritten 
+		if(isset($params['newpassword'])){ unset($params['newpassword']); }
 		if(isset($params['password']))
 		if(isset($params['password']))
 		{
 		{
-			$params['password'] = $this->generatePassword($params['password']);
+			if(empty($params['password']))
+			{ 
+				unset($params['password']); 
+			}
+			else
+			{
+				$params['password'] = $this->generatePassword($params['password']);
+			}
 		}
 		}
 		
 		
 		$update = array_merge($userdata, $params);
 		$update = array_merge($userdata, $params);
 		
 		
 		$this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update);
 		$this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update);
 
 
-		$_SESSION['user'] 	= $update['username'];
-		$_SESSION['role'] 	= $update['userrole'];
-
-		if(isset($update['firstname']))
-		{
-			$_SESSION['firstname'] = $update['firstname'];
-		}
-		if(isset($update['lastname']))
+		# if user updated his own profile, update session data
+		if($_SESSION['user'] == $params['username'])
 		{
 		{
-			$_SESSION['lastname'] = $update['lastname'];
+			$_SESSION['role'] 	= $update['userrole'];
+
+			if(isset($update['firstname']))
+			{
+				$_SESSION['firstname'] = $update['firstname'];
+			}
+			if(isset($update['lastname']))
+			{
+				$_SESSION['lastname'] = $update['lastname'];
+			}
 		}
 		}
 		
 		
 		return $userdata['username'];
 		return $userdata['username'];

+ 17 - 1
system/Models/WriteMeta.php

@@ -52,11 +52,18 @@ class WriteMeta extends WriteYaml
 
 
 		$description = $this->generateDescription($content, $parsedown, $item);
 		$description = $this->generateDescription($content, $parsedown, $item);
 
 
+		# owner holds the edit-rights
+		$owner = '';
+		if(isset($_SESSION['user']))
+		{
+			$owner = $_SESSION['user'];
+		}
+
 		$author = $settings['author'];
 		$author = $settings['author'];
 
 
 		if(isset($_SESSION))
 		if(isset($_SESSION))
 		{
 		{
-			if(isset($_SESSION['firstname']) && $_SESSION['firstname'] !='' && isset($_SESSION['lastname']) && $_SESSION['lastname'] != '')
+			if(isset($_SESSION['firstname']) && $_SESSION['firstname'] != '' && isset($_SESSION['lastname']) && $_SESSION['lastname'] != '')
 			{
 			{
 				$author = $_SESSION['firstname'] . ' ' . $_SESSION['lastname'];
 				$author = $_SESSION['firstname'] . ' ' . $_SESSION['lastname'];
 			}
 			}
@@ -71,6 +78,7 @@ class WriteMeta extends WriteYaml
 			'meta' => [
 			'meta' => [
 				'title' 		=> $title,
 				'title' 		=> $title,
 				'description' 	=> $description,
 				'description' 	=> $description,
+				'owner'			=> $owner,
 				'author' 		=> $author,
 				'author' 		=> $author,
 				'created'		=> date("Y-m-d"),
 				'created'		=> date("Y-m-d"),
 				'time'			=> date("H-i-s"),
 				'time'			=> date("H-i-s"),
@@ -88,6 +96,13 @@ class WriteMeta extends WriteYaml
 	# used by MetaApiController. Do not set title or description in defaults if page is not published yet
 	# used by MetaApiController. Do not set title or description in defaults if page is not published yet
 	public function getPageMetaBlank($content, $settings, $item)
 	public function getPageMetaBlank($content, $settings, $item)
 	{
 	{
+		# owner holds the edit-rights
+		$owner = '';
+		if(isset($_SESSION['user']))
+		{
+			$owner = $_SESSION['user'];
+		}
+
 		$author = $settings['author'];
 		$author = $settings['author'];
 
 
 		if(isset($_SESSION))
 		if(isset($_SESSION))
@@ -107,6 +122,7 @@ class WriteMeta extends WriteYaml
 			'meta' => [
 			'meta' => [
 				'title' 		=> '',
 				'title' 		=> '',
 				'description' 	=> '',
 				'description' 	=> '',
+				'owner'			=> $owner,
 				'author' 		=> $author,
 				'author' 		=> $author,
 				'created'		=> date("Y-m-d"),
 				'created'		=> date("Y-m-d"),
 				'time'			=> date("H-i-s"),
 				'time'			=> date("H-i-s"),

+ 11 - 9
system/Routes/Web.php

@@ -36,20 +36,22 @@ $app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->a
 $app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
 $app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
 $app->get('/tm/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
 $app->get('/tm/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
 
 
-$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new accessController($container['router'], $container['acl'], 'settings', 'view'));
-$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new accessController($container['router'], $container['acl'], 'settings', 'update'));
-$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new accessController($container['router'], $container['acl'], 'themes', 'view'));
-$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new accessController($container['router'], $container['acl'], 'themes', 'update'));
+$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new accessController($container['router'], $container['acl'], 'system', 'view'));
+$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new accessController($container['router'], $container['acl'], 'system', 'update'));
 
 
-$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new accessController($container['router'], $container['acl'], 'plugins', 'view'));
-$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new accessController($container['router'], $container['acl'], 'plugins', 'update'));
-$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new accessController($container['router'], $container['acl'], 'users', 'create'));
+$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new accessController($container['router'], $container['acl'], 'system', 'view'));
+$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new accessController($container['router'], $container['acl'], 'system', 'update'));
+
+$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new accessController($container['router'], $container['acl'], 'system', 'view'));
+$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new accessController($container['router'], $container['acl'], 'system', 'update'));
+
+$app->get('/tm/account', SettingsController::class . ':showAccount')->setName('user.account')->add(new accessController($container['router'], $container['acl'], 'user', 'view'));
+$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new accessController($container['router'], $container['acl'], 'user', 'create'));
 $app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new accessController($container['router'], $container['acl'], 'user', 'create'));
 $app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new accessController($container['router'], $container['acl'], 'user', 'create'));
 $app->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new accessController($container['router'], $container['acl'], 'user', 'update'));
 $app->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new accessController($container['router'], $container['acl'], 'user', 'update'));
 $app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new accessController($container['router'], $container['acl'], 'user', 'delete'));
 $app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new accessController($container['router'], $container['acl'], 'user', 'delete'));
-$app->get('/tm/user/account', SettingsController::class . ':showAccount')->setName('user.account')->add(new accessController($container['router'], $container['acl'], 'user', 'view'));
 $app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new accessController($container['router'], $container['acl'], 'user', 'view'));
 $app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new accessController($container['router'], $container['acl'], 'user', 'view'));
-$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new accessController($container['router'], $container['acl'], 'userlist', 'view'));
+$app->get('/tm/users', SettingsController::class . ':listUser')->setName('user.list')->add(new accessController($container['router'], $container['acl'], 'userlist', 'view'));
 
 
 $app->get('/tm/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new accessController($container['router'], $container['acl'], 'content', 'view'));
 $app->get('/tm/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new accessController($container['router'], $container['acl'], 'content', 'view'));
 $app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new accessController($container['router'], $container['acl'], 'content', 'view'));
 $app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new accessController($container['router'], $container['acl'], 'content', 'view'));

+ 10 - 12
system/Settings.php

@@ -190,32 +190,29 @@ class Settings
 	public static function loadResources()
 	public static function loadResources()
 	{
 	{
 		return ['content',
 		return ['content',
+				'mycontent',
 				'user',
 				'user',
 				'userlist',
 				'userlist',
-				'settings',
-				'themes',
-				'plugins'];
+				'system'];
 	}
 	}
 
 
 	public static function loadRolesAndPermissions()
 	public static function loadRolesAndPermissions()
 	{
 	{
-		$guest['name']			= 'guest';
-		$guest['inherits'] 		= NULL;
-		$guest['permissions']	= [];
-
 		$member['name']			= 'member';
 		$member['name']			= 'member';
-		$member['inherits'] 	= 'guest';
+		$member['inherits'] 	= NULL;
 		$member['permissions']	= ['user' => ['view','update','delete']];
 		$member['permissions']	= ['user' => ['view','update','delete']];
 
 
 		$author['name']			= 'author';
 		$author['name']			= 'author';
 		$author['inherits']		= 'member';
 		$author['inherits']		= 'member';
-		$author['permissions']	= ['content' => ['view','create', 'update', 'delete']];
+		$author['permissions']	= ['mycontent' => ['view', 'create', 'update'],
+								   'content' => ['view']];
 
 
 		$editor['name']			= 'editor';
 		$editor['name']			= 'editor';
 		$editor['inherits']		= 'author';
 		$editor['inherits']		= 'author';
-		$editor['permissions']	= ['content' => ['publish', 'depublish']];
+		$editor['permissions']	= [ 'mycontent' => ['delete', 'publish', 'unpublish'],
+									'content' => ['create', 'update', 'delete', 'publish', 'unpublish']];
 
 
-		return [$guest, $member, $author, $editor];
+		return [$member, $author, $editor];
 	}
 	}
 
 
 	public static function createAcl($roles, $resources)
 	public static function createAcl($roles, $resources)
@@ -235,7 +232,8 @@ class Settings
 		foreach($roles as $role)
 		foreach($roles as $role)
 		{
 		{
 			$acl->addRole(new Role($role['name']), $role['inherits']);
 			$acl->addRole(new Role($role['name']), $role['inherits']);
-			foreach($role['permissions'] as $resource =>  $permissions)
+
+			foreach($role['permissions'] as $resource => $permissions)
 			{
 			{
 				$acl->allow($role['name'], $resource, $permissions);
 				$acl->allow($role['name'], $resource, $permissions);
 			}
 			}

+ 1 - 1
system/Translations.php

@@ -9,7 +9,7 @@ class Translations
     $yaml = new Models\WriteYaml();
     $yaml = new Models\WriteYaml();
     $settings = $yaml->getYaml('settings', 'settings.yaml');
     $settings = $yaml->getYaml('settings', 'settings.yaml');
 
 
-    if($settings === FALSE){
+    if(!isset($settings['language'])){
       $language = \Typemill\Settings::whichLanguage();
       $language = \Typemill\Settings::whichLanguage();
     } else {
     } else {
       $language = $settings['language'];
       $language = $settings['language'];

+ 31 - 2
system/author/css/style.css

@@ -720,6 +720,9 @@ li.row{
 	width: 100%;
 	width: 100%;
 	box-sizing: border-box;
 	box-sizing: border-box;
 }
 }
+li.row.header{
+	display:none;
+}
 li.row ul{
 li.row ul{
 	background: #f9f8f6;
 	background: #f9f8f6;
 	margin: 5px 0;
 	margin: 5px 0;
@@ -738,6 +741,15 @@ li.col.username, li.col.email, li.col.userrole, li.col.edit{
 li.col.username{
 li.col.username{
 	border-top: 2px solid #70c1b3;
 	border-top: 2px solid #70c1b3;
 }
 }
+li.col:before{
+	width: 25%;
+	font-weight:900;
+	display:inline-block;
+}
+li.col.username:before{content:"User: ";}
+li.col.userrole:before{content:"Role: ";}
+li.col.email:before{content:"Mail: ";}
+li.col.edit:before{content:"Link: ";}
 .col.username,.col.email,.col.userrole, .col.edit{
 .col.username,.col.email,.col.userrole, .col.edit{
 	width: 100%;
 	width: 100%;
 }
 }
@@ -2781,6 +2793,23 @@ footer a:focus, footer a:hover, footer a:active
 		margin-top: 10px;
 		margin-top: 10px;
 		margin-bottom: 40px;
 		margin-bottom: 40px;
 	}
 	}
+	li.row.header{
+		display: block;
+	}
+	li.row.header ul{
+		color: #fff;
+		background: #70c1b3;
+	}
+	li.col.username, li.col.email, li.col.userrole, li.col.edit{
+		border: 1px solid #fff;
+	}
+	li.col.username{
+		border-top: 2px solid #70c1b3;
+	}
+	li.col:before{
+		width: 0%;
+		display:none;
+	}
 	li.row ul{
 	li.row ul{
 		margin: 0px;
 		margin: 0px;
 	}
 	}
@@ -2789,10 +2818,10 @@ footer a:focus, footer a:hover, footer a:active
 	}
 	}
 	.col.edit{
 	.col.edit{
 		width: 10%;
 		width: 10%;
-	}	
+	}
 	li.col.username{
 	li.col.username{
 		border-top: 0px;
 		border-top: 0px;
-		border-left: 2px solid #70c1b3;
+		border-left: 0px solid #70c1b3;
 	}
 	}
 	.buttonset{
 	.buttonset{
 		width: 76%;
 		width: 76%;

+ 16 - 8
system/author/editor/editor-blox.twig

@@ -5,6 +5,8 @@
 
 
 	<div class="formWrapper">
 	<div class="formWrapper">
 
 
+	  {% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
+
 		<div id="metanav" class="metanav" v-cloak>
 		<div id="metanav" class="metanav" v-cloak>
 		  
 		  
 		  <button
 		  <button
@@ -26,6 +28,8 @@
 
 
 		</div>
 		</div>
 
 
+      {% endif %}
+      
 		<section id="blox" :class="showBlox">
 		<section id="blox" :class="showBlox">
 
 
 			<div class="blox-body">
 			<div class="blox-body">
@@ -60,17 +64,21 @@
 						</content-block>
 						</content-block>
 					</draggable>
 					</draggable>
 				</div>
 				</div>
-	
-				<div class="format-bar">
 
 
-					<content-block :body="false">
+				{% if (acl.isAllowed(get_role(), 'content', 'edit')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
 
 
-						<button v-for="button in formats" class="format-item"  @click.prevent="setData( $event, button.component )" data-id="99999" id="blox-99999" :title="button.title|translate" v-html="button.label"></button>
-					
-					</content-block>
+						<div class="format-bar">
+
+							<content-block :body="false">
+
+								<button v-for="button in formats" class="format-item"  @click.prevent="setData( $event, button.component )" data-id="99999" id="blox-99999" :title="button.title|translate" v-html="button.label"></button>
+							
+							</content-block>
+
+						</div>
+
+				{% endif %}
 
 
-				</div>
-				
 			</div>
 			</div>
 			
 			
 		</section>
 		</section>

+ 4 - 0
system/author/editor/editor-raw.twig

@@ -5,6 +5,8 @@
 	
 	
 	<div class="formWrapper">
 	<div class="formWrapper">
 
 
+	  {% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
+
 		<div id="metanav" class="metanav" v-cloak>
 		<div id="metanav" class="metanav" v-cloak>
 		  
 		  
 		  <button
 		  <button
@@ -26,6 +28,8 @@
 
 
 		</div>
 		</div>
 
 
+      {% endif %}		
+
 		<div id="editor" class="editor">
 		<div id="editor" class="editor">
 			<form action="#" v-cloak>
 			<form action="#" v-cloak>
 			
 			

+ 38 - 21
system/author/editor/publish-controller.twig

@@ -2,30 +2,47 @@
 
 
 <div class="editor buttonset" id="publishController" data-published="{{ item.published }}" data-drafted="{{ item.drafted }}" v-cloak>
 <div class="editor buttonset" id="publishController" data-published="{{ item.published }}" data-drafted="{{ item.drafted }}" v-cloak>
 	<div v-if="errors.message" class="message error">${ errors.message }</div>
 	<div v-if="errors.message" class="message error">${ errors.message }</div>
-	<button v-if="raw" @click.prevent="saveDraft" id="draft" :class="draftResult" :disabled="draftDisabled"><span class="desktop">{{ __('Save') }}&nbsp;</span>{{ __('Draft') }}</button><button @click.prevent="publishDraft" id="publish" :class="publishResult" :disabled="publishDisabled">{{ __('Publish') }}</button>
-	<button @click.prevent="showModal('discard')" v-if="visual && !publishStatus" id="discard" :class="discardResult" :disabled="publishDisabled">{{ __('Discard') }}</button>
+	{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
+		<button v-if="raw" @click.prevent="saveDraft" id="draft" :class="draftResult" :disabled="draftDisabled"><span class="desktop">{{ __('Save') }}&nbsp;</span>{{ __('Draft') }}</button>
+	{% endif %}
+	{% if (acl.isAllowed(get_role(), 'content', 'publish')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'publish')) ) %}
+		<button @click.prevent="publishDraft" id="publish" :class="publishResult" :disabled="publishDisabled">{{ __('Publish') }}</button>
+	{% endif %}
+	{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
+		<button @click.prevent="showModal('discard')" v-if="visual && !publishStatus" id="discard" :class="discardResult" :disabled="publishDisabled">{{ __('Discard') }}</button>
+	{% endif %}
 	<div class="secondary">
 	<div class="secondary">
-		<button @click.prevent="depublishArticle" class="button--secondary" :disabled="publishStatus"><span class="desktop">${publishLabel}</span><span class="mobile">${publishLabelMobile}</span></button>
-		<button @click.prevent="showModal('delete')" class="button--secondary danger"><span class="desktop">{{ __('delete') }}</span><span class="mobile">X</span></button>
-		<a v-if="visual" href="{{ base_url }}/tm/content/raw{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('raw mode') }}</span><span class="mobile">{{ __('raw') }}</span></a>
-		<a v-if="raw" href="{{ base_url }}/tm/content/visual{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('visual mode') }}</span><span class="mobile">{{ __('visual') }}</span></a>
+		{% if (acl.isAllowed(get_role(), 'content', 'unpublish')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'unpublish')) ) %}
+			<button @click.prevent="depublishArticle" class="button--secondary" :disabled="publishStatus"><span class="desktop">${publishLabel}</span><span class="mobile">${publishLabelMobile}</span></button>
+		{% endif %}
+		{% if (acl.isAllowed(get_role(), 'content', 'delete')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'delete')) ) %}
+			<button @click.prevent="showModal('delete')" class="button--secondary danger"><span class="desktop">{{ __('delete') }}</span><span class="mobile">X</span></button>
+		{% endif %}
+		{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
+			<a v-if="visual" href="{{ base_url }}/tm/content/raw{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('raw mode') }}</span><span class="mobile">{{ __('raw') }}</span></a>
+			<a v-if="raw" href="{{ base_url }}/tm/content/visual{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('visual mode') }}</span><span class="mobile">{{ __('visual') }}</span></a>
+		{% endif %}
 		<a target="_blank" class="button--secondary" href="{{ item.urlAbs }}"><svg class="icon baseline icon-external-link"><use xlink:href="#icon-external-link"></use></svg></a>
 		<a target="_blank" class="button--secondary" href="{{ item.urlAbs }}"><svg class="icon baseline icon-external-link"><use xlink:href="#icon-external-link"></use></svg></a>
 	</div>
 	</div>
-	<transition name="fade">
-		<div v-if="modalWindow" id="modalWindow" class="modalWindow">
-			<div class="modalInner">
-				<div @click="hideModal" id="closeModal" class="closeModal">X</div>
-				<div v-if="modalType == 'delete'">
-					<h2>{{ __('Delete page') }}</h2>
-					<p>{{ __('Do you really want to delete this page') }} {{ __('Please confirm') }}</p>
-					<button @click.prevent="deleteArticle" class="large alert" :class="deleteResult" :disabled="deleteDisabled">{{ __('Delete Page') }}</button>
-				</div>
-				<div v-if="modalType == 'discard'">
-					<h2>{{ __('Discard Changes') }}</h2>
-					<p>{{ __('Do you want to discard your changes and set the content back to the live version') }}</p>
-					<button @click.prevent="discardDraft" class="large fullwidth" :class="discardResult" :disabled="publishDisabled">{{ __('Discard Changes') }}</button>
+		<transition name="fade">
+			<div v-if="modalWindow" id="modalWindow" class="modalWindow">
+				<div class="modalInner">
+					<div @click="hideModal" id="closeModal" class="closeModal">X</div>
+					{% if (acl.isAllowed(get_role(), 'content', 'delete')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'delete')) ) %}
+						<div v-if="modalType == 'delete'">
+							<h2>{{ __('Delete page') }}</h2>
+							<p>{{ __('Do you really want to delete this page') }} {{ __('Please confirm') }}</p>
+							<button @click.prevent="deleteArticle" class="large alert" :class="deleteResult" :disabled="deleteDisabled">{{ __('Delete Page') }}</button>
+						</div>
+					{% endif %}
+					{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
+						<div v-if="modalType == 'discard'">
+							<h2>{{ __('Discard Changes') }}</h2>
+							<p>{{ __('Do you want to discard your changes and set the content back to the live version') }}</p>
+							<button @click.prevent="discardDraft" class="large fullwidth" :class="discardResult" :disabled="publishDisabled">{{ __('Discard Changes') }}</button>
+						</div>
+					{% endif %}
 				</div>
 				</div>
 			</div>
 			</div>
-		</div>
-	</transition>
+		</transition>
 </div>
 </div>

+ 5 - 1
system/author/js/vue-blox.js

@@ -401,6 +401,7 @@ const contentComponent = Vue.component('content-block', {
 				if(httpStatus == 400)
 				if(httpStatus == 400)
 				{
 				{
 					self.activatePage();
 					self.activatePage();
+					publishController.errors.message = "Looks like you are logged out. Please login and try again.";
 				}
 				}
 				if(response)
 				if(response)
 				{
 				{
@@ -410,7 +411,7 @@ const contentComponent = Vue.component('content-block', {
 	
 	
 					if(result.errors)
 					if(result.errors)
 					{
 					{
-						publishController.errors.message = result.errors;
+						publishController.errors.message = result.errors.message;
 					}
 					}
 					else
 					else
 					{	
 					{	
@@ -1587,6 +1588,7 @@ const imageComponent = Vue.component('image-component', {
 							if(httpStatus == 400)
 							if(httpStatus == 400)
 							{
 							{
 								self.activatePage();
 								self.activatePage();
+								publishController.errors.message = "Looks like you are logged out. Please login and try again.";
 							}
 							}
 							if(response)
 							if(response)
 							{
 							{
@@ -1777,6 +1779,7 @@ const fileComponent = Vue.component('file-component', {
 							if(httpStatus == 400)
 							if(httpStatus == 400)
 							{
 							{
 								self.activatePage();
 								self.activatePage();
+								publishController.errors.message = "Looks like you are logged out. Please login and try again.";
 							}
 							}
 							if(response)
 							if(response)
 							{
 							{
@@ -2316,6 +2319,7 @@ let editor = new Vue({
 	el: '#blox',
 	el: '#blox',
 /*	components: componentList, */
 /*	components: componentList, */
 	data: {
 	data: {
+		errors: [],
 		root: document.getElementById("main").dataset.url,
 		root: document.getElementById("main").dataset.url,
 		html: false,
 		html: false,
 		title: false,
 		title: false,

+ 4 - 0
system/author/js/vue-meta.js

@@ -516,6 +516,10 @@ let meta = new Vue({
 	            {
 	            {
 	            	self.formErrors = error.response.data.errors;
 	            	self.formErrors = error.response.data.errors;
 	            }
 	            }
+	            if(error.response.data.errors.message)
+	            {
+	            	publishController.errors.message = error.response.data.errors.message;
+	            }
 	        });
 	        });
 		},
 		},
 	}
 	}

+ 7 - 8
system/author/js/vue-publishcontroller.js

@@ -87,7 +87,7 @@ let publishController = new Vue({
 					self.publishDisabled 	= false;
 					self.publishDisabled 	= false;
 					self.publishResult 		= "fail";
 					self.publishResult 		= "fail";
 					self.errors.message 	= "Something went wrong, please refresh the page and try again."					
 					self.errors.message 	= "Something went wrong, please refresh the page and try again."					
-				}				
+				}
 			}, method, url, this.form );
 			}, method, url, this.form );
 		},
 		},
 		discardDraft: function(e) {
 		discardDraft: function(e) {
@@ -118,7 +118,6 @@ let publishController = new Vue({
 					{
 					{
 						self.publishDisabled = false;
 						self.publishDisabled = false;
 						self.discardResult   = "fail";
 						self.discardResult   = "fail";
-						
 						if(result.errors.title){ editor.errors.title = result.errors.title[0] }
 						if(result.errors.title){ editor.errors.title = result.errors.title[0] }
 						if(result.errors.content){ editor.errors.content = result.errors.content[0] }
 						if(result.errors.content){ editor.errors.content = result.errors.content[0] }
 						if(result.errors.message){ self.errors.message = result.errors.message }
 						if(result.errors.message){ self.errors.message = result.errors.message }
@@ -212,12 +211,6 @@ let publishController = new Vue({
 					self.publishResult 		= "fail";
 					self.publishResult 		= "fail";
 					self.errors.message 	= "You are probably logged out. Please backup your changes, login and then try again."
 					self.errors.message 	= "You are probably logged out. Please backup your changes, login and then try again."
 				}
 				}
-				else if(httpStatus != 200)
-				{
-					self.publishDisabled 	= false;
-					self.publishResult 		= "fail";
-					self.errors.message 	= "Something went wrong, please refresh the page and try again."					
-				}
 				else if(response)
 				else if(response)
 				{
 				{
 					var result = JSON.parse(response);
 					var result = JSON.parse(response);
@@ -236,6 +229,12 @@ let publishController = new Vue({
 						navi.getNavi();
 						navi.getNavi();
 					}
 					}
 				}
 				}
+				else if(httpStatus != 200)
+				{
+					self.publishDisabled 	= false;
+					self.publishResult 		= "fail";
+					self.errors.message 	= "Something went wrong, please refresh the page and try again.";
+				}				
 			}, method, url, this.form );
 			}, method, url, this.form );
 		},
 		},
 		deleteArticle: function(e){
 		deleteArticle: function(e){

+ 7 - 2
system/author/metatabs.yaml

@@ -18,11 +18,16 @@ meta:
     heroimagealt:
     heroimagealt:
       type: text
       type: text
       label: Alternative Text for the hero image
       label: Alternative Text for the hero image
+    owner:
+      type: text
+      label: owner (username)
+      class: medium
+      description: Has edit rights for this article.
     author:
     author:
       type: text
       type: text
       label: author
       label: author
-      class: large
-      description: Taken from your user account if set.
+      class: medium
+      description: Can be used for author line in frontend.
     manualdate:
     manualdate:
       type: date
       type: date
       label: Manual date
       label: Manual date

+ 10 - 10
system/author/partials/aside.twig

@@ -1,12 +1,12 @@
 <nav id="sidebar-menu" class="sidebar-menu">
 <nav id="sidebar-menu" class="sidebar-menu">
-	{% if is_role('administrator') %}
-		<div id="mobile-menu" class="menu-action">{{ __('Menu') }} <span class="button-arrow"></span></div>
-		<ul class="list pa0 ma0">
-			<li class="pb1"><a class="link dark-gray hover-bg-white bl bw2 b--near-white hover-b--tm-green pa2 dib w-100{{ (route == 'settings.show') ? ' active' : '' }}" href="{{ path_for('settings.show') }}"><svg class="icon icon-wrench mr2"><use xlink:href="#icon-wrench"></use></svg> {{ __('System') }}</a></li>
-			<li class="pb1"><a class="link dark-gray hover-bg-white bl bw2 b--near-white hover-b--tm-green pa2 dib w-100{{ (route == 'themes.show') ? ' active' : '' }}" href="{{ path_for('themes.show') }}"><svg class="icon icon-paint-brush mr2"><use xlink:href="#icon-paint-brush"></use></svg> {{ __('Themes') }}</a></li>
-			<li class="pb1"><a class="link dark-gray hover-bg-white bl bw2 b--near-white hover-b--tm-green pa2 dib w-100{{ (route == 'plugins.show') ? ' active' : '' }}" href="{{ path_for('plugins.show') }}"><svg class="icon icon-plug mr2"><use xlink:href="#icon-plug"></use></svg> {{ __('Plugins') }}</a></li>
-			<li class="pb1"><a class="link dark-gray hover-bg-white bl bw2 b--near-white hover-b--tm-green pa2 dib w-100{{ (route == 'user.account') ? ' active' : '' }}" href="{{ path_for('user.account') }}"><svg class="icon icon-user mr2"><use xlink:href="#icon-user"></use></svg> {{ __('Account') }}</a></li>
-			<li class="pb1"><a class="link dark-gray hover-bg-white bl bw2 b--near-white hover-b--tm-green pa2 dib w-100{{ (route == 'user.list') ? ' active' : '' }}" href="{{ path_for('user.list') }}"><svg class="icon icon-group mr2"><use xlink:href="#icon-group"></use></svg> {{ __('Users') }}</a></li>
-		</ul>
-	{% endif %}
+	<div id="mobile-menu" class="menu-action">{{ __('Menu') }} <span class="button-arrow"></span></div>
+	<ul class="list pa0 ma0">
+
+		{% for name,navitem in navigation %}
+			{% if acl.isAllowed(get_role(), navitem.aclresource, navitem.aclprivilege) %}
+				<li class="pb1"><a class="link dark-gray hover-bg-white bl bw2 b--near-white hover-b--tm-green pa2 dib w-100{{ navitem.active ? ' active' : '' }}" href="{{ path_for(navitem.routename) }}"><svg class="icon baseline {{ navitem.icon }} mr2"><use xlink:href="#{{ navitem.icon }}"></use></svg> {{ __(name) }}</a></li>
+			{% endif %}
+		{% endfor %}
+
+	</ul>
 </nav>
 </nav>

+ 9 - 4
system/author/partials/navi.twig

@@ -1,13 +1,18 @@
+{% set content = (current_url matches '/content.*/') ? true : false %}
+
 <nav class="header-navi">
 <nav class="header-navi">
 	<div class="logo">
 	<div class="logo">
 		<a href="{{ base_url }}/tm/content/{{ settings.editor }}">Typemill</a>
 		<a href="{{ base_url }}/tm/content/{{ settings.editor }}">Typemill</a>
 	</div>
 	</div>
 	<ul class="navi-items">
 	<ul class="navi-items">
-		<li><a href="{{ base_url }}/tm/content/{{ settings.editor }}"{{ navigation ? ' class="active"' : '' }}><svg class="icon baseline icon-file-text-o"><use xlink:href="#icon-file-text-o"></use></svg><span class="nav-label"> {{ __('Content') }}</span></a></li><li>
-			{% if is_role('administrator') %}
-				<a href="{{ path_for('settings.show') }}"{{ users ? ' class="active"' : '' }}><svg class="icon baseline icon-cog"><use xlink:href="#icon-cog"></use></svg><span class="nav-label"> {{ __('Settings') }}</span></a></li><li>
+		<li>
+			{% if acl.isAllowed(get_role(), 'content', 'view') %}
+				<a href="{{ base_url }}/tm/content/{{ settings.editor }}"{{ content ? ' class="active"' : '' }}><svg class="icon baseline icon-file-text-o"><use xlink:href="#icon-file-text-o"></use></svg><span class="nav-label"> {{ __('Content') }}</span></a></li><li>
+			{% endif %}
+			{% if acl.isAllowed(get_role(), 'system', 'view') %}
+				<a href="{{ path_for('settings.show') }}"{{ content ? '' : 'class="active"' }}><svg class="icon baseline icon-cog"><use xlink:href="#icon-cog"></use></svg><span class="nav-label"> {{ __('Settings') }}</span></a></li><li>
 			{% else %}
 			{% else %}
-				<a href="{{ path_for('user.show', {'username' : get_username() }) }}"{{ users ? ' class="active"' : '' }}><svg class="icon icon-cog baseline"><use xlink:href="#icon-cog, gear"></use></svg><span class="nav-label">  {{ __('Account') }}</span></a></li><li>			
+				<a href="{{ path_for('user.account') }}"{{ content ? '' : 'class="active"' }}><svg class="icon icon-cog baseline"><use xlink:href="#icon-cog"></use></svg><span class="nav-label">  {{ __('Settings') }}</span></a></li><li>			
 			{% endif %}
 			{% endif %}
 			<a href="{{ base_url }}"><svg class="icon baseline icon-external-link"><use xlink:href="#icon-external-link"></use></svg><span class="nav-label"> {{ __('View Site') }}</span></a></li><li>
 			<a href="{{ base_url }}"><svg class="icon baseline icon-external-link"><use xlink:href="#icon-external-link"></use></svg><span class="nav-label"> {{ __('View Site') }}</span></a></li><li>
 			<a href="{{ path_for('auth.logout') }}"><svg class="icon baseline icon-power-off"><use xlink:href="#icon-power-off"></use></svg><span class="nav-label">  {{ __('Logout') }}</span></a></li>
 			<a href="{{ path_for('auth.logout') }}"><svg class="icon baseline icon-power-off"><use xlink:href="#icon-power-off"></use></svg><span class="nav-label">  {{ __('Logout') }}</span></a></li>

+ 2 - 2
system/author/settings/user.twig

@@ -22,13 +22,13 @@
 							<fieldset class="subfield">
 							<fieldset class="subfield">
 								<legend>{{ field.legend }}</legend>
 								<legend>{{ field.legend }}</legend>
 								{% for field in field.fields %}
 								{% for field in field.fields %}
-									{% include '/partials/fields.twig' with { 'settings': usersettings, 'object' : 'user', 'itemName' : userdata.username, 'class' : 'large' } %}
+									{% include '/partials/fields.twig' with { 'settings': usersettings, 'object' : 'users', 'itemName' : 'user', 'class' : 'large' } %}
 								{% endfor %}
 								{% endfor %}
 							</fieldset>
 							</fieldset>
 						
 						
 							{% else %}
 							{% else %}
 								
 								
-								{% include '/partials/fields.twig' with { 'settings': usersettings, 'object' : 'user', 'itemName' : userdata.username, 'class' : 'large' } %}
+								{% include '/partials/fields.twig' with { 'settings': usersettings, 'object' : 'users', 'itemName' : 'user', 'class' : 'large' } %}
 
 
 							{% endif %}
 							{% endif %}
 								
 								

+ 8 - 0
system/author/settings/userlist.twig

@@ -12,6 +12,14 @@
 			</header>
 			</header>
 			
 			
 			<ul class="userlist">
 			<ul class="userlist">
+				<li class="row header">
+					<ul>
+						<li class="col username">Username
+						</li><li class="col userrole">Role
+						</li><li class="col email">E-Mail
+						</li><li class="col edit">Edit</li>
+					</ul>
+				</li>
 				{% for user in userdata %}
 				{% for user in userdata %}
 				
 				
 					<li class="row">
 					<li class="row">

+ 10 - 26
system/author/settings/usernew.twig

@@ -23,30 +23,6 @@
 						{% endif %}
 						{% endif %}
 					</div>
 					</div>
 
 
-					<div class="large{{ errors.firstname ? ' errors' : '' }}">
-						<label for="firstname">{{ __('First Name') }}</label>
-						<input type="text" name="firstname" value="{{ old.firstname ? old.firstname : userdata.firstname }}">
-						{% if errors.firstname %}
-							<span class="error">{{ errors.firstname | first }}</span>
-						{% endif %}
-					</div>
-
-					<div class="large{{ errors.lastname ? ' errors' : '' }}">
-						<label for="lastname">{{ __('Last Name') }}</label>
-						<input type="text" name="lastname" value="{{ old.lastname ? old.lastname : userdata.lastname }}">
-						{% if errors.lastname %}
-							<span class="error">{{ errors.lastname | first }}</span>
-						{% endif %}
-					</div>					
-
-					<div class="large{{ errors.email ? ' errors' : '' }}">
-						<label for="email">{{ __('E-Mail') }} <abbr title="{{ __('required') }}">*</abbr></label>
-						<input type="text" name="email" value="{{ old.email ? old.email : '' }}" required>
-						{% if errors.email %}
-							<span class="error">{{ errors.email | first }}</span>
-						{% endif %}
-					</div>
-
 					<div class="large{{ errors.userrole ? ' errors' : '' }}">
 					<div class="large{{ errors.userrole ? ' errors' : '' }}">
 						<label for="userrole">{{ __('Role') }} <abbr title="{{ __('required') }}">*</abbr></label>
 						<label for="userrole">{{ __('Role') }} <abbr title="{{ __('required') }}">*</abbr></label>
 						<select name="userrole" required>
 						<select name="userrole" required>
@@ -57,7 +33,15 @@
 						{% if errors.userrole %}
 						{% if errors.userrole %}
 							<span class="error">{{ errors.userrole | first }}</span>
 							<span class="error">{{ errors.userrole | first }}</span>
 						{% endif %}
 						{% endif %}
-					</div>					
+					</div>
+
+					<div class="large{{ errors.email ? ' errors' : '' }}">
+						<label for="email">{{ __('E-Mail') }} <abbr title="{{ __('required') }}">*</abbr></label>
+						<input type="text" name="email" value="{{ old.email ? old.email : '' }}" required>
+						{% if errors.email %}
+							<span class="error">{{ errors.email | first }}</span>
+						{% endif %}
+					</div>
 					
 					
 					<div class="large{{ errors.password ? ' errors' : '' }}">
 					<div class="large{{ errors.password ? ' errors' : '' }}">
 						<label for="password">{{ __('Password') }} <abbr title="{{ __('required') }}">*</abbr></label>
 						<label for="password">{{ __('Password') }} <abbr title="{{ __('required') }}">*</abbr></label>
@@ -76,4 +60,4 @@
 		</form>		
 		</form>		
 	</div>	
 	</div>	
 	
 	
-{% endblock %}
+{% endblock %}