فهرست منبع

Merge branch 'feature138' into develop

trendschau 5 سال پیش
والد
کامیت
296b5ac9da
41فایلهای تغییر یافته به همراه1292 افزوده شده و 456 حذف شده
  1. 2 1
      composer.json
  2. 106 1
      composer.lock
  3. 6 1
      content/01-cyanine-theme/03-content-elements.yaml
  4. 154 8
      system/Controllers/ArticleApiController.php
  5. 9 4
      system/Controllers/AuthController.php
  6. 76 11
      system/Controllers/BlockApiController.php
  7. 26 2
      system/Controllers/ContentBackendController.php
  8. 19 0
      system/Controllers/ContentController.php
  9. 12 0
      system/Controllers/MediaApiController.php
  10. 16 0
      system/Controllers/MetaApiController.php
  11. 403 235
      system/Controllers/SettingsController.php
  12. 3 3
      system/Controllers/SetupController.php
  13. 14 0
      system/Events/OnResourcesLoaded.php
  14. 14 0
      system/Events/OnRolesPermissionsLoaded.php
  15. 14 0
      system/Events/OnSystemnaviLoaded.php
  16. 14 0
      system/Events/OnUserfieldsLoaded.php
  17. 10 0
      system/Extensions/TwigUserExtension.php
  18. 3 0
      system/Middleware/RedirectIfNoAdmin.php
  19. 37 0
      system/Middleware/accessController.php
  20. 1 1
      system/Models/Fields.php
  21. 32 11
      system/Models/User.php
  22. 17 1
      system/Models/WriteMeta.php
  23. 26 20
      system/Routes/Web.php
  24. 60 1
      system/Settings.php
  25. 1 1
      system/Translations.php
  26. 35 3
      system/author/css/style.css
  27. 16 8
      system/author/editor/editor-blox.twig
  28. 4 0
      system/author/editor/editor-raw.twig
  29. 38 21
      system/author/editor/publish-controller.twig
  30. 5 1
      system/author/js/vue-blox.js
  31. 4 0
      system/author/js/vue-meta.js
  32. 7 8
      system/author/js/vue-publishcontroller.js
  33. 16 0
      system/author/layouts/layout.twig
  34. 7 2
      system/author/metatabs.yaml
  35. 10 17
      system/author/partials/aside.twig
  36. 2 2
      system/author/partials/fields.twig
  37. 9 4
      system/author/partials/navi.twig
  38. 18 63
      system/author/settings/user.twig
  39. 8 0
      system/author/settings/userlist.twig
  40. 10 26
      system/author/settings/usernew.twig
  41. 28 0
      system/system.php

+ 2 - 1
composer.json

@@ -19,7 +19,8 @@
 		"erusev/parsedown": "~1.4",
 		"erusev/parsedown": "~1.4",
 		"erusev/parsedown-extra": "dev-master",
 		"erusev/parsedown-extra": "dev-master",
 		"jbroadway/urlify": "1.1.3",
 		"jbroadway/urlify": "1.1.3",
-		"vlucas/valitron": "dev-master"
+		"vlucas/valitron": "dev-master",
+        "laminas/laminas-permissions-acl": "^2.7"
     },
     },
 	"autoload": {
 	"autoload": {
 		"psr-4": {
 		"psr-4": {

+ 106 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
         "This file is @generated automatically"
     ],
     ],
-    "content-hash": "b2dd4b8a1c943c5e407add9f1b9104ea",
+    "content-hash": "87094a87b3a795ce73c299e4535358fb",
     "packages": [
     "packages": [
         {
         {
             "name": "erusev/parsedown",
             "name": "erusev/parsedown",
@@ -159,6 +159,111 @@
             ],
             ],
             "time": "2019-06-13T18:30:56+00:00"
             "time": "2019-06-13T18:30:56+00:00"
         },
         },
+        {
+            "name": "laminas/laminas-permissions-acl",
+            "version": "2.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laminas/laminas-permissions-acl.git",
+                "reference": "624567fe376a70e0bfb5aa8217d5afa13b9d6e61"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laminas/laminas-permissions-acl/zipball/624567fe376a70e0bfb5aa8217d5afa13b9d6e61",
+                "reference": "624567fe376a70e0bfb5aa8217d5afa13b9d6e61",
+                "shasum": ""
+            },
+            "require": {
+                "laminas/laminas-zendframework-bridge": "^1.0",
+                "php": "^5.6 || ^7.0"
+            },
+            "replace": {
+                "zendframework/zend-permissions-acl": "self.version"
+            },
+            "require-dev": {
+                "laminas/laminas-coding-standard": "~1.0.0",
+                "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3",
+                "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5"
+            },
+            "suggest": {
+                "laminas/laminas-servicemanager": "To support Laminas\\Permissions\\Acl\\Assertion\\AssertionManager plugin manager usage"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.7.x-dev",
+                    "dev-develop": "2.8.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laminas\\Permissions\\Acl\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "description": "Provides a lightweight and flexible access control list (ACL) implementation for privileges management",
+            "homepage": "https://laminas.dev",
+            "keywords": [
+                "acl",
+                "laminas"
+            ],
+            "time": "2019-12-31T17:37:23+00:00"
+        },
+        {
+            "name": "laminas/laminas-zendframework-bridge",
+            "version": "1.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laminas/laminas-zendframework-bridge.git",
+                "reference": "fcd87520e4943d968557803919523772475e8ea3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/fcd87520e4943d968557803919523772475e8ea3",
+                "reference": "fcd87520e4943d968557803919523772475e8ea3",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1",
+                "squizlabs/php_codesniffer": "^3.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev",
+                    "dev-develop": "1.1.x-dev"
+                },
+                "laminas": {
+                    "module": "Laminas\\ZendFrameworkBridge"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/autoload.php"
+                ],
+                "psr-4": {
+                    "Laminas\\ZendFrameworkBridge\\": "src//"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "description": "Alias legacy ZF class names to Laminas Project equivalents.",
+            "keywords": [
+                "ZendFramework",
+                "autoloading",
+                "laminas",
+                "zf"
+            ],
+            "time": "2020-05-20T16:45:56+00:00"
+        },
         {
         {
             "name": "nikic/fast-route",
             "name": "nikic/fast-route",
             "version": "v1.3.0",
             "version": "v1.3.0",

+ 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

+ 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")
 		{
 		{

+ 403 - 235
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,11 +236,19 @@ class SettingsController extends Controller
 		}
 		}
 		
 		
 		/* add the users for navigation */
 		/* add the users for navigation */
-		$user		= new User();
-		$users		= $user->getUsers();
-		$route 		= $request->getAttribute('route');
-
-		return $this->render($response, 'settings/themes.twig', array('settings' => $userSettings, 'themes' => $themedata, 'users' => $users, '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)
@@ -301,11 +313,19 @@ class SettingsController extends Controller
 			}
 			}
 		}
 		}
 		
 		
-		$user 	= new User();
-		$users 	= $user->getUsers();
 		$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, 'users' => $users, 'route' => $route->getName() ));
+		return $this->render($response, 'settings/plugins.twig', array(
+			'settings' 		=> $userSettings,
+			'acl' 			=> $this->c->acl,
+			'navigation' 	=> $navigation,
+			'plugins' 		=> $plugins,
+			'route' 		=> $route->getName() 
+		));
 	}
 	}
 
 
 	/*************************************
 	/*************************************
@@ -315,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;
@@ -414,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();
@@ -485,119 +481,64 @@ class SettingsController extends Controller
 		}
 		}
 	}
 	}
 
 
-	private function validateInput($objectType, $objectName, $userInput, $validate)
-	{
-		/* 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;
-	}
+	/***********************
+	**   USER MANAGEMENT  **
+	***********************/
 
 
-	protected function saveImages($imageFields, $userInput, $userSettings, $files)
+	public function showAccount($request, $response, $args)
 	{
 	{
+		$username 	= $_SESSION['user'];
 
 
-		# initiate image processor with standard image sizes
-		$processImages = new ProcessImage($userSettings['images']);
-
-		if(!$processImages->checkFolders())
-		{
-			$this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.');
-			return false; 
-		}
-
-		foreach($imageFields as $fieldName => $imageField)
+		$validate 	= new Validation();
+		
+		if($validate->username($username))
 		{
 		{
-			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;
+			# get settings
+			$settings 	= $this->c->get('settings');
 
 
-					if($processImages->createImage($imageSrc, $image->getClientFilename(), $userSettings['images'], $overwrite = NULL))
-					{
-						# returns image path to media library
-						$userInput[$fieldName] = $processImages->publishImage();
-					}
-			    }
-			}
+			# get user with userdata
+			$user 		= new User();
+			$userdata 	= $user->getSecureUser($username);
+			
+			# instantiate field-builder
+			$fieldsModel	= new Fields();
+
+			# get the field-definitions
+			$fieldDefinitions = $this->getUserFields($userdata['userrole']);
+
+			# prepare userdata for field-builder
+			$userSettings['users']['user'] = $userdata;
+
+			# generate the input form
+			$userform = $fieldsModel->getFields($userSettings, 'users', 'user', $fieldDefinitions);
+
+			$route = $request->getAttribute('route');
+			$navigation = $this->getNavigation();
+
+			# set navigation active
+			$navigation['Account']['active'] = true;
+
+			return $this->render($response, 'settings/user.twig', array(
+				'settings' 		=> $settings,
+				'acl' 			=> $this->c->acl,
+				'navigation'	=> $navigation, 
+				'usersettings' 	=> $userSettings, 		// needed for image url in form, will overwrite settings for field-template
+				'userform' 		=> $userform, 			// field model, needed to generate frontend-field
+				'userdata' 		=> $userdata, 			// needed to fill form with data
+#				'userrole' 		=> false,				// not needed ? 
+#				'username' 		=> $args['username'], 	// not needed ?
+				'route' 		=> $route->getName()  	// needed to set link active
+			));			
 		}
 		}
-		return $userInput;
+		
+		$this->c->flash->addMessage('error', 'User does not exists');
+		return $response->withRedirect($this->c->router->pathFor('home'));
 	}
 	}
-
-	/***********************
-	**   USER MANAGEMENT  **
-	***********************/
 	
 	
 	public function showUser($request, $response, $args)
 	public function showUser($request, $response, $args)
 	{
 	{
-		if($_SESSION['role'] == 'editor' && $_SESSION['user'] !== $args['username'])
+		# if user has no rights to watch userlist, then redirect to 
+		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']] ));
 		}
 		}
@@ -606,73 +547,115 @@ class SettingsController extends Controller
 		
 		
 		if($validate->username($args['username']))
 		if($validate->username($args['username']))
 		{
 		{
-			$user 		= new User();
-			$users		= $user->getUsers();
-			$userrole	= $user->getUserroles();
-			$userdata 	= $user->getUser($args['username']);
+			# get settings
 			$settings 	= $this->c->get('settings');
 			$settings 	= $this->c->get('settings');
+
+			# get user with userdata
+			$user 		= new User();
+			$userdata 	= $user->getSecureUser($args['username']);
+
+			$username	= $userdata['username'];
 			
 			
-			if($userdata)
-			{				
-				return $this->render($response, 'settings/user.twig', array('settings' => $settings, 'users' => $users, 'userdata' => $userdata, 'userrole' => $userrole, 'username' => $args['username'] ));
-			}
+			# instantiate field-builder
+			$fieldsModel	= new Fields();
+
+			# get the field-definitions
+			$fieldDefinitions = $this->getUserFields($userdata['userrole']);
+
+			# prepare userdata for field-builder
+			$userSettings['users']['user'] = $userdata;
+
+			# generate the input form
+			$userform = $fieldsModel->getFields($userSettings, 'users', 'user', $fieldDefinitions);
+
+			$route = $request->getAttribute('route');
+			$navigation = $this->getNavigation();
+
+			# set navigation active
+			$navigation['Users']['active'] = true;
+			
+			return $this->render($response, 'settings/user.twig', array(
+				'settings' 		=> $settings,
+				'acl' 			=> $this->c->acl, 
+				'navigation'	=> $navigation, 
+				'usersettings' 	=> $userSettings, 		// needed for image url in form, will overwrite settings for field-template
+				'userform' 		=> $userform, 			// field model, needed to generate frontend-field
+				'userdata' 		=> $userdata, 			// needed to fill form with data
+#				'userrole' 		=> false,				// not needed ? 
+#				'username' 		=> $args['username'], 	// not needed ?
+				'route' 		=> $route->getName()  	// needed to set link active
+			));
 		}
 		}
 		
 		
 		$this->c->flash->addMessage('error', 'User does not exists');
 		$this->c->flash->addMessage('error', 'User does not exists');
-		return $response->withRedirect($this->c->router->pathFor('user.list'));
+		return $response->withRedirect($this->c->router->pathFor('user.account'));
 	}
 	}
 
 
 	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();
-			$userroles		= $user->getUserroles();
 			$validate		= new Validation();
 			$validate		= new Validation();
+			$userroles 		= $this->c->acl->getRoles();
 
 
 			if($validate->newUser($params, $userroles))
 			if($validate->newUser($params, $userroles))
 			{
 			{
-				$userdata	= array('username' => $params['username'], 'firstname' => $params['firstname'], 'lastname' => $params['lastname'], 'email' => $params['email'], 'userrole' => $params['userrole'], 'password' => $params['password']);
+				$userdata	= array(
+					'username' 		=> $params['username'], 
+					'email' 		=> $params['email'], 
+					'userrole' 		=> $params['userrole'], 
+					'password' 		=> $params['password']);
 				
 				
 				$user->createUser($userdata);
 				$user->createUser($userdata);
 
 
@@ -687,89 +670,107 @@ class SettingsController extends Controller
 	
 	
 	public function updateUser($request, $response, $args)
 	public function updateUser($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();
+			$userdata 		= $params['user'];
 			$user 			= new User();
 			$user 			= new User();
-			$userroles		= $user->getUserroles();
 			$validate		= new Validation();
 			$validate		= new Validation();
+			$userroles 		= $this->c->acl->getRoles();
 			
 			
-			/* non admins have different update rights */
-			if($_SESSION['role'] !== 'administrator')
+			$redirectRoute	= ($userdata['username'] == $_SESSION['user']) ? $this->c->router->pathFor('user.account') : $this->c->router->pathFor('user.show', ['username' => $userdata['username']]);
+
+			# 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 update other userdata than its own */
-				if($_SESSION['user'] !== $params['username'])
+				# if an editor tries to update other userdata than its own */
+				if($_SESSION['user'] !== $userdata['username'])
 				{
 				{
-					return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] ));
+					return $response->withRedirect($this->c->router->pathFor('user.account'));
 				}
 				}
 				
 				
-				/* non admins cannot change his userrole */
-				$params['userrole'] = $_SESSION['role'];
+				# non admins cannot change their userrole, so set it to session-value
+				$userdata['userrole'] = $_SESSION['role'];
 			}
 			}
-	
-			if($validate->existingUser($params, $userroles))
+
+			# validate standard fields for users
+			if($validate->existingUser($userdata, $userroles))
 			{
 			{
-				$userdata	= array('username' => $params['username'], 'firstname' => $params['firstname'], 'lastname' => $params['lastname'], 'email' => $params['email'], 'userrole' => $params['userrole']);
-				
-				if(empty($params['password']) AND empty($params['newpassword']))
+				# validate custom input fields and return images
+				$userfields = $this->getUserFields($userdata['userrole']);
+				$imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields);
+
+				if(!empty($imageFields))
 				{
 				{
+					$images = $request->getUploadedFiles();
+
+					if(isset($images['user']))
+					{
+						# set image size
+						$settings = $this->c->get('settings');
+						$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 */
+				if(isset($_SESSION['errors']))
+				{
+					$this->c->flash->addMessage('error', 'Please correct the errors');
+					return $response->withRedirect($redirectRoute);
+				}
+
+				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' => $params['username']]));
+					return $response->withRedirect($redirectRoute);
 				}
 				}
-				elseif($validate->newPassword($params))
+				elseif($validate->newPassword($userdata))
 				{
 				{
-					$userdata['password'] = $params['newpassword'];				
+					$userdata['password'] = $userdata['newpassword'];
+					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' => $params['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' => $params['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'));
 				}				
 				}				
 			}
 			}
 			
 			
@@ -785,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');
@@ -793,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'];
@@ -836,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/OnResourcesLoaded.php

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

+ 14 - 0
system/Events/OnRolesPermissionsLoaded.php

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

+ 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);
 	}
 	}
+	*/
 }
 }

+ 37 - 0
system/Middleware/accessController.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Typemill\Middleware;
+
+use Slim\Interfaces\RouterInterface;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class accessController
+{
+	protected $router;
+	
+	public function __construct(RouterInterface $router, $acl, $resource, $privilege)
+	{
+		$this->router 		= $router;
+		$this->acl 			= $acl;
+		$this->resource 	= $resource;
+		$this->privilege 	= $privilege;
+	}
+
+	public function __invoke(Request $request, Response $response, $next)
+	{
+		if(!isset($_SESSION['login']))
+		{
+			return $response->withRedirect($this->router->pathFor('auth.show'));
+		}
+
+		if(!$this->acl->isAllowed($_SESSION['role'], $this->resource, $this->privilege ))
+		{
+			# redirect to frontend startpage
+			# alternatively return an error and show an error page.
+			return $response->withRedirect($this->router->pathFor('home'));
+		}
+
+		return $next($request, $response);
+	}
+}

+ 1 - 1
system/Models/Fields.php

@@ -78,7 +78,7 @@ class Fields
 					}
 					}
 				}
 				}
 				elseif($field->getType() == "checkbox")
 				elseif($field->getType() == "checkbox")
-				{					
+				{
 					# checkboxes need a special treatment, because field does not exist in settings if unchecked by user
 					# checkboxes need a special treatment, because field does not exist in settings if unchecked by user
 					if(isset($userSettings[$objectType][$objectName][$fieldName]))
 					if(isset($userSettings[$objectType][$objectName][$fieldName]))
 					{
 					{

+ 32 - 11
system/Models/User.php

@@ -29,6 +29,13 @@ class User extends WriteYaml
 		$user = $this->getYaml('settings/users', $username . '.yaml');
 		$user = $this->getYaml('settings/users', $username . '.yaml');
 		return $user;
 		return $user;
 	}
 	}
+
+	public function getSecureUser($username)
+	{
+		$user = $this->getYaml('settings/users', $username . '.yaml');
+		unset($user['password']);
+		return $user;
+	}
 	
 	
 	public function createUser($params)
 	public function createUser($params)
 	{		
 	{		
@@ -59,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']))
+		# if user updated his own profile, update session data
+		if($_SESSION['user'] == $params['username'])
 		{
 		{
-			$_SESSION['firstname'] = $update['firstname'];
-		}
-		if(isset($update['lastname']))
-		{
-			$_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'];
@@ -91,11 +110,13 @@ class User extends WriteYaml
 		}
 		}
 	}
 	}
 	
 	
+	/* replaced by ACL
 	public function getUserroles()
 	public function getUserroles()
 	{
 	{
 		return array('administrator', 'editor');
 		return array('administrator', 'editor');
 	}	
 	}	
-	
+	*/
+
 	public function login($username)
 	public function login($username)
 	{
 	{
 		$user = $this->getUser($username);
 		$user = $this->getUser($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"),

+ 26 - 20
system/Routes/Web.php

@@ -9,6 +9,7 @@ use Typemill\Controllers\ContentBackendController;
 use Typemill\Middleware\RedirectIfUnauthenticated;
 use Typemill\Middleware\RedirectIfUnauthenticated;
 use Typemill\Middleware\RedirectIfAuthenticated;
 use Typemill\Middleware\RedirectIfAuthenticated;
 use Typemill\Middleware\RedirectIfNoAdmin;
 use Typemill\Middleware\RedirectIfNoAdmin;
+use Typemill\Middleware\accessController;
 
 
 if($settings['settings']['setup'])
 if($settings['settings']['setup'])
 {
 {
@@ -35,37 +36,42 @@ $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 RedirectIfNoAdmin($container['router'], $container['flash']));
-$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
-$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
-$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
-$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
-$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
-$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
-$app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
+$app->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->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
-$app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
-$app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
-$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
+$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/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
-$app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
-$app->get('/tm/content[/{params:.*}]', ContentBackendController::class . ':showEmpty')->setName('content.empty')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
+$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/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->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new accessController($container['router'], $container['acl'], 'user', '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/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new accessController($container['router'], $container['acl'], 'content', 'view'));
+$app->get('/tm/content[/{params:.*}]', ContentBackendController::class . ':showEmpty')->setName('content.empty')->add(new accessController($container['router'], $container['acl'], 'content', 'view'));
 
 
 foreach($routes as $pluginRoute)
 foreach($routes as $pluginRoute)
 {
 {
-	$method = $pluginRoute['httpMethod'];
-	$route	= $pluginRoute['route'];
-	$class	= $pluginRoute['class'];
+	$method 	= $pluginRoute['httpMethod'];
+	$route		= $pluginRoute['route'];
+	$class		= $pluginRoute['class'];
+	$resource 	= isset($pluginRoute['resource']) ? $pluginRoute['resource'] : NULL;
+	$privilege 	= isset($pluginRoute['privilege']) ? $pluginRoute['privilege'] : NULL;
 
 
 	if(isset($pluginRoute['name']))
 	if(isset($pluginRoute['name']))
 	{
 	{
-		$app->{$method}($route, $class)->setName($pluginRoute['name']);
+		$app->{$method}($route, $class)->setName($pluginRoute['name'])->add(new accessController($container['router'], $container['acl'], $resource, $privilege));
 	}
 	}
 	else
 	else
 	{
 	{
-		$app->{$method}($route, $class);
+		$app->{$method}($route, $class)->add(new accessController($container['router'], $container['acl'], $resource, $privilege));
 	}
 	}
 }
 }
 
 

+ 60 - 1
system/Settings.php

@@ -2,6 +2,10 @@
 
 
 namespace Typemill;
 namespace Typemill;
 
 
+use Laminas\Permissions\Acl\Acl;
+use Laminas\Permissions\Acl\Role\GenericRole as Role;
+use Laminas\Permissions\Acl\Resource\GenericResource as Resource;
+
 class Settings
 class Settings
 {	
 {	
 	public static function loadSettings()
 	public static function loadSettings()
@@ -182,4 +186,59 @@ class Settings
 			$yaml->updateYaml('settings', 'settings.yaml', $settings);					
 			$yaml->updateYaml('settings', 'settings.yaml', $settings);					
 		}
 		}
 	}
 	}
-}
+
+	public static function loadResources()
+	{
+		return ['content',
+				'mycontent',
+				'user',
+				'userlist',
+				'system'];
+	}
+
+	public static function loadRolesAndPermissions()
+	{
+		$member['name']			= 'member';
+		$member['inherits'] 	= NULL;
+		$member['permissions']	= ['user' => ['view','update','delete']];
+
+		$author['name']			= 'author';
+		$author['inherits']		= 'member';
+		$author['permissions']	= ['mycontent' => ['view', 'create', 'update'],
+								   'content' => ['view']];
+
+		$editor['name']			= 'editor';
+		$editor['inherits']		= 'author';
+		$editor['permissions']	= [ 'mycontent' => ['delete', 'publish', 'unpublish'],
+									'content' => ['create', 'update', 'delete', 'publish', 'unpublish']];
+
+		return [$member, $author, $editor];
+	}
+
+	public static function createAcl($roles, $resources)
+	{
+		$acl = new Acl();
+
+		foreach($resources as $resource)
+		{
+			$acl->addResource(new Resource($resource));
+		}
+
+		# add administrator role
+		$acl->addRole(new Role('administrator'));
+		$acl->allow('administrator');
+
+		# add all other roles dynamically
+		foreach($roles as $role)
+		{
+			$acl->addRole(new Role($role['name']), $role['inherits']);
+
+			foreach($role['permissions'] as $resource => $permissions)
+			{
+				$acl->allow($role['name'], $resource, $permissions);
+			}
+		}
+
+		return $acl;
+	}
+}

+ 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'];

+ 35 - 3
system/author/css/style.css

@@ -67,9 +67,12 @@ a, a:link, a:visited, a:focus, a:hover, a:active, .link, button, .button, .tab-b
 .hover-bg-tm-green:hover{
 .hover-bg-tm-green:hover{
 	background:#66b0a3;
 	background:#66b0a3;
 }
 }
-.hover-b--tm-green:hover{
+.hover-b--tm-green:hover,.hover-b--tm-green.active{
 	border-color:#66b0a3;
 	border-color:#66b0a3;
 }
 }
+.hover-bg-white.active{
+	background-color: #fff;
+}
 .w-100{
 .w-100{
 	width:100%!important;
 	width:100%!important;
 }
 }
@@ -717,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;
@@ -735,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%;
 }
 }
@@ -2778,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;
 	}
 	}
@@ -2786,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){

+ 16 - 0
system/author/layouts/layout.twig

@@ -41,6 +41,22 @@
 					<title>power-off</title>
 					<title>power-off</title>
 					<path d="M24 14c0 6.609-5.391 12-12 12s-12-5.391-12-12c0-3.797 1.75-7.297 4.797-9.578 0.891-0.672 2.141-0.5 2.797 0.391 0.672 0.875 0.484 2.141-0.391 2.797-2.031 1.531-3.203 3.859-3.203 6.391 0 4.406 3.594 8 8 8s8-3.594 8-8c0-2.531-1.172-4.859-3.203-6.391-0.875-0.656-1.062-1.922-0.391-2.797 0.656-0.891 1.922-1.062 2.797-0.391 3.047 2.281 4.797 5.781 4.797 9.578zM14 2v10c0 1.094-0.906 2-2 2s-2-0.906-2-2v-10c0-1.094 0.906-2 2-2s2 0.906 2 2z"></path>
 					<path d="M24 14c0 6.609-5.391 12-12 12s-12-5.391-12-12c0-3.797 1.75-7.297 4.797-9.578 0.891-0.672 2.141-0.5 2.797 0.391 0.672 0.875 0.484 2.141-0.391 2.797-2.031 1.531-3.203 3.859-3.203 6.391 0 4.406 3.594 8 8 8s8-3.594 8-8c0-2.531-1.172-4.859-3.203-6.391-0.875-0.656-1.062-1.922-0.391-2.797 0.656-0.891 1.922-1.062 2.797-0.391 3.047 2.281 4.797 5.781 4.797 9.578zM14 2v10c0 1.094-0.906 2-2 2s-2-0.906-2-2v-10c0-1.094 0.906-2 2-2s2 0.906 2 2z"></path>
 				</symbol>
 				</symbol>
+				<symbol id="icon-user" viewBox="0 0 20 28">
+					<path d="M20 21.859c0 2.281-1.5 4.141-3.328 4.141h-13.344c-1.828 0-3.328-1.859-3.328-4.141 0-4.109 1.016-8.859 5.109-8.859 1.266 1.234 2.984 2 4.891 2s3.625-0.766 4.891-2c4.094 0 5.109 4.75 5.109 8.859zM16 8c0 3.313-2.688 6-6 6s-6-2.688-6-6 2.688-6 6-6 6 2.688 6 6z"></path>
+				</symbol>
+				<symbol id="icon-group" viewBox="0 0 30 28">
+					<path d="M9.266 14c-1.625 0.047-3.094 0.75-4.141 2h-2.094c-1.563 0-3.031-0.75-3.031-2.484 0-1.266-0.047-5.516 1.937-5.516 0.328 0 1.953 1.328 4.062 1.328 0.719 0 1.406-0.125 2.078-0.359-0.047 0.344-0.078 0.688-0.078 1.031 0 1.422 0.453 2.828 1.266 4zM26 23.953c0 2.531-1.672 4.047-4.172 4.047h-13.656c-2.5 0-4.172-1.516-4.172-4.047 0-3.531 0.828-8.953 5.406-8.953 0.531 0 2.469 2.172 5.594 2.172s5.063-2.172 5.594-2.172c4.578 0 5.406 5.422 5.406 8.953zM10 4c0 2.203-1.797 4-4 4s-4-1.797-4-4 1.797-4 4-4 4 1.797 4 4zM21 10c0 3.313-2.688 6-6 6s-6-2.688-6-6 2.688-6 6-6 6 2.688 6 6zM30 13.516c0 1.734-1.469 2.484-3.031 2.484h-2.094c-1.047-1.25-2.516-1.953-4.141-2 0.812-1.172 1.266-2.578 1.266-4 0-0.344-0.031-0.688-0.078-1.031 0.672 0.234 1.359 0.359 2.078 0.359 2.109 0 3.734-1.328 4.062-1.328 1.984 0 1.937 4.25 1.937 5.516zM28 4c0 2.203-1.797 4-4 4s-4-1.797-4-4 1.797-4 4-4 4 1.797 4 4z"></path>
+				</symbol>
+				<symbol id="icon-wrench" viewBox="0 0 26 28">
+					<path d="M6 23c0-0.547-0.453-1-1-1s-1 0.453-1 1 0.453 1 1 1 1-0.453 1-1zM16.063 16.438l-10.656 10.656c-0.359 0.359-0.875 0.578-1.406 0.578s-1.047-0.219-1.422-0.578l-1.656-1.687c-0.375-0.359-0.594-0.875-0.594-1.406s0.219-1.047 0.594-1.422l10.641-10.641c0.812 2.047 2.453 3.687 4.5 4.5zM25.969 9.641c0 0.516-0.187 1.156-0.359 1.656-0.984 2.781-3.656 4.703-6.609 4.703-3.859 0-7-3.141-7-7s3.141-7 7-7c1.141 0 2.625 0.344 3.578 0.984 0.156 0.109 0.25 0.25 0.25 0.438 0 0.172-0.109 0.344-0.25 0.438l-4.578 2.641v3.5l3.016 1.672c0.516-0.297 4.141-2.578 4.453-2.578s0.5 0.234 0.5 0.547z"></path>
+				</symbol>
+				<symbol id="icon-plug" viewBox="0 0 28 28">
+					<path d="M27.422 7.078c0.766 0.781 0.766 2.047 0 2.828l-6.266 6.25 2.344 2.344-2.5 2.5c-3.422 3.422-8.641 3.906-12.516 1.344l-5.656 5.656h-2.828v-2.828l5.656-5.656c-2.562-3.875-2.078-9.094 1.344-12.516l2.5-2.5 2.344 2.344 6.25-6.266c0.781-0.766 2.047-0.766 2.828 0 0.781 0.781 0.781 2.063 0 2.828l-6.25 6.266 3.656 3.656 6.266-6.25c0.781-0.781 2.047-0.781 2.828 0z"></path>
+				</symbol>
+				<symbol id="icon-paint-brush" viewBox="0 0 28 28">
+					<path d="M25.234 0c1.422 0 2.734 1.062 2.734 2.547 0 0.828-0.328 1.625-0.703 2.359-1.219 2.312-5.313 9.953-7.266 11.75-0.953 0.891-2.078 1.422-3.406 1.422-2.641 0-4.797-2.25-4.797-4.875 0-1.25 0.516-2.469 1.437-3.313l9.969-9.047c0.547-0.5 1.266-0.844 2.031-0.844zM11.031 16.156c0.812 1.578 2.297 2.766 4.016 3.219l0.016 1.109c0.094 4.453-3 7.516-7.469 7.516-5.297 0-7.594-4.219-7.594-9.016 0.578 0.391 2.594 2 3.25 2 0.391 0 0.719-0.219 0.859-0.578 1.328-3.469 3.406-4.094 6.922-4.25z"></path>
+				</symbol>
+				{{ assets.renderSvg() }}
 			</defs>
 			</defs>
 		</svg>
 		</svg>
 		
 		

+ 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 - 17
system/author/partials/aside.twig

@@ -1,19 +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>
-		<h3>{{ __('Settings') }}</h3>
-		<ul class="menu-list margin-bottom">
-			<li class="menu-item"><a href="{{ path_for('settings.show') }}"{{ (route == 'settings.show') ? ' class="active"' : '' }}>{{ __('System') }}</a></li>
-			<li class="menu-item"><a href="{{ path_for('themes.show') }}"{{ (route == 'themes.show') ? ' class="active"' : '' }}>{{ __('Themes') }}</a></li>
-			<li class="menu-item"><a href="{{ path_for('plugins.show') }}"{{ (route == 'plugins.show') ? ' class="active"' : '' }}>{{ __('Plugins') }}</a></li>
-		</ul>
-		<h3>{{ __('Users') }}</h3>
-		<ul class="menu-list">
-			<li class="menu-item"><a href="{{ path_for('user.list') }}"{{ (route == 'user.list') ? ' class="active"' : '' }}>{{ __('All users') }}</a></li>
-			<li class="menu-item"><a href="{{ path_for('user.new') }}"{{  (route == 'user.new') ? ' class="active"' : '' }}>{{ __('Create user') }}</a></li>
-			{% for user in users %}
-				<li class="menu-item"><a href="{{ path_for('user.show', {'username' : user }) }}"{{ (username == user) ? ' class="active"' : '' }}>{{ user }}</a></li>
-			{% endfor %}
-		</ul>
-	{% endif %}
+	<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>

+ 2 - 2
system/author/partials/fields.twig

@@ -1,5 +1,5 @@
-<div class="cardField{{ errors[itemName][field.name] ? ' error' : '' }}{{field.fieldsize ? ' ' ~ field.fieldsize : ''}}">
-
+<div class="{{ class ? class : 'cardField' }}{{ errors[itemName][field.name] ? ' error' : '' }}{{field.fieldsize ? ' ' ~ field.fieldsize : ''}}">
+	
 	<label for="{{ itemName }}[{{ field.name }}]">{{ __( field.getLabel() ) }}
 	<label for="{{ itemName }}[{{ field.name }}]">{{ __( field.getLabel() ) }}
 		{% if field.getAttribute('required') %}<strong><abbr title="{{ __('required') }}">*</abbr></strong>{% endif %}
 		{% if field.getAttribute('required') %}<strong><abbr title="{{ __('required') }}">*</abbr></strong>{% endif %}
 		{% if field.help %}<div class="help">?<span class="tooltip">{{__(field.help|slice(0,100))}}</span></div>{% endif %}
 		{% if field.help %}<div class="help">?<span class="tooltip">{{__(field.help|slice(0,100))}}</span></div>{% endif %}

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

+ 18 - 63
system/author/settings/user.twig

@@ -5,79 +5,35 @@
 	
 	
 	<div class="formWrapper">
 	<div class="formWrapper">
 
 
-		<form id="userform" method="POST" action="{{ path_for('user.update') }}">
+		<form id="userform" method="POST" action="{{ path_for('user.update') }}" enctype="multipart/form-data">
 		
 		
 			<section id="user" class="settings">
 			<section id="user" class="settings">
 
 
 				<header class="headline">
 				<header class="headline">
-					<h1>{{ __('Edit User') }}</h1>
+					<h1>{{ userdata.username }}</h1>
 				</header>
 				</header>
 				
 				
 				<fieldset class="auth">
 				<fieldset class="auth">
-				
-					<div class="large{{ errors.username ? ' errors' : '' }}">
-						<label for="username">{{ __('Username') }} <small>({{ __('not editable') }})</small></label>
-						<input type="text" name="showusername" value="{{ old.username ? old.username : userdata.username }}" required disabled>
-						<input type="hidden" name="username" value="{{ userdata.username }}">
-						{% if errors.username %}
-							<span class="error">{{ errors.username | first }}</span>
-						{% endif %}
-					</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 : userdata.email }}" required>
-						{% if errors.email %}
-							<span class="error">{{ errors.email | first }}</span>
-						{% endif %}
-					</div>
+					{% for field in userform %}
 
 
-					{% if is_role('administrator') %}
-						<div class="large{{ errors.userrole ? ' errors' : '' }}">
-							<label for="userrole">{{ __('Role') }} <abbr title="{{ __('required') }}">*</abbr></label>
-							<select name="userrole" required>
-								{% for role in userrole %}
-									<option value="{{ role }}"{% if (role == old.userrole or role == userdata.userrole) %} selected{% endif %}>{{ role }}</option>
+						{% if field.type == 'fieldset' %}
+											
+							<fieldset class="subfield">
+								<legend>{{ field.legend }}</legend>
+								{% for field in field.fields %}
+									{% include '/partials/fields.twig' with { 'settings': usersettings, 'object' : 'users', 'itemName' : 'user', 'class' : 'large' } %}
 								{% endfor %}
 								{% endfor %}
-							</select>
-							{% if errors.userrole %}
-								<span class="error">{{ errors.userrole | first }}</span>
+							</fieldset>
+						
+							{% else %}
+								
+								{% include '/partials/fields.twig' with { 'settings': usersettings, 'object' : 'users', 'itemName' : 'user', 'class' : 'large' } %}
+
 							{% endif %}
 							{% endif %}
-						</div>
-					{% endif %}
-					
-					<div class="large{{ errors.password ? ' errors' : '' }}">
-						<label for="password">{{ __('Actual Password') }}</label>
-						<input type="password" name="password">
-						{% if errors.password %}
-							<span class="error">{{ errors.password | first }}</span>
-						{% endif %}
-					</div>
+								
+						{% endfor %}
 
 
-					<div class="large{{ errors.newpassword ? ' errors' : '' }}">
-						<label for="newpassword">{{ __('New Password') }}</label>
-						<input type="password" name="newpassword">
-						{% if errors.newpassword %}
-							<span class="error">{{ errors.newpassword | first }}</span>
-						{% endif %}
-					</div>
-					
 				</fieldset>
 				</fieldset>
 
 
 			</section>
 			</section>
@@ -87,7 +43,7 @@
 			<div class="actionLink">
 			<div class="actionLink">
 				<a href="#" id="openModal" class="openModal">{{ __('Delete User') }}</a>
 				<a href="#" id="openModal" class="openModal">{{ __('Delete User') }}</a>
 			</div>
 			</div>
-		</form>		
+		</form>
 	</div>
 	</div>
 	
 	
 	<div id="modalWindow" class="modal">
 	<div id="modalWindow" class="modal">
@@ -103,5 +59,4 @@
 		</div>
 		</div>
 	</div>
 	</div>
 	
 	
-	
 {% endblock %}
 {% endblock %}

+ 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 %}

+ 28 - 0
system/system.php

@@ -3,6 +3,8 @@
 use Typemill\Events\OnSettingsLoaded;
 use Typemill\Events\OnSettingsLoaded;
 use Typemill\Events\OnPluginsLoaded;
 use Typemill\Events\OnPluginsLoaded;
 use Typemill\Events\OnSessionSegmentsLoaded;
 use Typemill\Events\OnSessionSegmentsLoaded;
+use Typemill\Events\OnRolesPermissionsLoaded;
+use Typemill\Events\OnResourcesLoaded;
 
 
 /****************************
 /****************************
 * HIDE ERRORS BY DEFAULT	  *
 * HIDE ERRORS BY DEFAULT	  *
@@ -109,6 +111,32 @@ $dispatcher->dispatch('onPluginsLoaded', new OnPluginsLoaded($pluginNames));
 # dispatch settings event and get all setting-updates from plugins
 # dispatch settings event and get all setting-updates from plugins
 $dispatcher->dispatch('onSettingsLoaded', new OnSettingsLoaded($settings))->getData();
 $dispatcher->dispatch('onSettingsLoaded', new OnSettingsLoaded($settings))->getData();
 
 
+
+/**********************************
+* 	LOAD ROLES AND PERMISSIONS 	  *
+**********************************/
+
+# load roles and permissions
+$rolesAndPermissions = Typemill\Settings::loadRolesAndPermissions();
+
+# dispatch roles so plugins can enhance them
+$rolesAndPermissions = $dispatcher->dispatch('onRolesPermissionsLoaded', new OnRolesPermissionsLoaded($rolesAndPermissions))->getData();
+
+# load resources
+$resources = Typemill\Settings::loadResources();
+
+# dispatch roles so plugins can enhance them
+$resources = $dispatcher->dispatch('onResourcesLoaded', new OnResourcesLoaded($resources))->getData();
+
+# create acl-object
+$acl = Typemill\Settings::createAcl($rolesAndPermissions, $resources);
+
+# add acl to container
+$container['acl'] = function($c) use ($acl)
+{
+	return $acl;
+};
+
 /******************************
 /******************************
 * ADD DISPATCHER TO CONTAINER *
 * ADD DISPATCHER TO CONTAINER *
 ******************************/
 ******************************/