diff --git a/content/01-cyanine-theme/03-content-elements.yaml b/content/01-cyanine-theme/03-content-elements.yaml index cf490a6..abcd5bc 100644 --- a/content/01-cyanine-theme/03-content-elements.yaml +++ b/content/01-cyanine-theme/03-content-elements.yaml @@ -1,8 +1,13 @@ meta: 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" + heroimage: null + heroimagealt: null + owner: testauthor author: trendschau + manualdate: null + modified: '2020-07-09' created: '2020-06-11' time: 21-05-02 navtitle: 'content elements' - modified: '2020-06-11' + hide: false diff --git a/media/live/hostinger-1.png b/media/live/hostinger-1.png deleted file mode 100644 index 8768d5c..0000000 Binary files a/media/live/hostinger-1.png and /dev/null differ diff --git a/media/live/hostinger-2.png b/media/live/hostinger-2.png deleted file mode 100644 index 8768d5c..0000000 Binary files a/media/live/hostinger-2.png and /dev/null differ diff --git a/media/live/hostinger.png b/media/live/hostinger.png deleted file mode 100644 index 8768d5c..0000000 Binary files a/media/live/hostinger.png and /dev/null differ diff --git a/media/live/logo.png b/media/live/logo.png deleted file mode 100644 index b5b44e2..0000000 Binary files a/media/live/logo.png and /dev/null differ diff --git a/media/original/hostinger-1.png b/media/original/hostinger-1.png deleted file mode 100644 index 8768d5c..0000000 Binary files a/media/original/hostinger-1.png and /dev/null differ diff --git a/media/original/hostinger-2.png b/media/original/hostinger-2.png deleted file mode 100644 index 8768d5c..0000000 Binary files a/media/original/hostinger-2.png and /dev/null differ diff --git a/media/original/hostinger.png b/media/original/hostinger.png deleted file mode 100644 index 8768d5c..0000000 Binary files a/media/original/hostinger.png and /dev/null differ diff --git a/media/original/logo.png b/media/original/logo.png deleted file mode 100644 index 165e05e..0000000 Binary files a/media/original/logo.png and /dev/null differ diff --git a/media/thumbs/hostinger-1.png b/media/thumbs/hostinger-1.png deleted file mode 100644 index 693c1a9..0000000 Binary files a/media/thumbs/hostinger-1.png and /dev/null differ diff --git a/media/thumbs/hostinger-2.png b/media/thumbs/hostinger-2.png deleted file mode 100644 index 693c1a9..0000000 Binary files a/media/thumbs/hostinger-2.png and /dev/null differ diff --git a/media/thumbs/hostinger.png b/media/thumbs/hostinger.png deleted file mode 100644 index 693c1a9..0000000 Binary files a/media/thumbs/hostinger.png and /dev/null differ diff --git a/media/thumbs/logo.png b/media/thumbs/logo.png deleted file mode 100644 index 8b3ae82..0000000 Binary files a/media/thumbs/logo.png and /dev/null differ diff --git a/system/Controllers/ArticleApiController.php b/system/Controllers/ArticleApiController.php index 4b66eed..b2fc511 100644 --- a/system/Controllers/ArticleApiController.php +++ b/system/Controllers/ArticleApiController.php @@ -24,6 +24,12 @@ class ArticleApiController extends ContentController $this->params = $request->getParams(); $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 if($this->params['raw']) { @@ -35,6 +41,16 @@ class ArticleApiController extends ContentController # set item 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 $this->setPublishStatus(); @@ -100,12 +116,28 @@ class ArticleApiController extends ContentController $this->params = $request->getParams(); $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 if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); } # set item 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 $this->setPublishStatus(); @@ -178,17 +210,32 @@ class ArticleApiController extends ContentController $this->params = $request->getParams(); $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 if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); } # set item 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 $delete = $this->deleteContentFiles(['txt']); # set redirect url to edit page - $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor']; if(isset($this->item->urlRelWoF)) { @@ -217,6 +264,12 @@ class ArticleApiController extends ContentController $this->params = $request->getParams(); $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 $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor']; @@ -225,6 +278,16 @@ class ArticleApiController extends ContentController # set item 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') { @@ -275,16 +338,32 @@ class ArticleApiController extends ContentController # get params from call $this->params = $request->getParams(); $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 if(!$this->validateEditorInput()){ return $response->withJson($this->errors,422); } # set structure if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); } - + # set item 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) $this->setItemPath('txt'); @@ -319,6 +398,12 @@ class ArticleApiController extends ContentController # get params from call $this->params = $request->getParams(); $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 = 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); } + # 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($this->params['parent_id_to'] == 'navi') { @@ -397,7 +495,7 @@ class ArticleApiController extends ContentController } $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 $this->setStructure($draft = true, $cache = false); @@ -427,6 +525,12 @@ class ArticleApiController extends ContentController $this->params = $request->getParams(); $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 = 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); } # 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 $folderKeyPath = explode('.', $this->params['folder_id']); @@ -442,7 +546,7 @@ class ArticleApiController extends ContentController # get the item from structure $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']; $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); } - # get extended structure $extended = $write->getYaml('cache', 'structure-extended.yaml'); @@ -494,6 +597,12 @@ class ArticleApiController extends ContentController $this->params = $request->getParams(); $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 = false; @@ -608,6 +717,12 @@ class ArticleApiController extends ContentController # get params from call $this->params = $request->getParams(); $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 = false; @@ -730,12 +845,28 @@ class ArticleApiController extends ContentController /* get params from call */ $this->params = $request->getParams(); $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 if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); } /* set item */ 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 $this->setPublishStatus(); @@ -778,12 +909,27 @@ class ArticleApiController extends ContentController $this->params = $request->getParams(); $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 if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); } /* set item */ 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 $this->setPublishStatus(); diff --git a/system/Controllers/AuthController.php b/system/Controllers/AuthController.php index b8c8921..b908f02 100644 --- a/system/Controllers/AuthController.php +++ b/system/Controllers/AuthController.php @@ -125,10 +125,15 @@ class AuthController extends Controller $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')); } } diff --git a/system/Controllers/BlockApiController.php b/system/Controllers/BlockApiController.php index 1d55d0d..07d6857 100644 --- a/system/Controllers/BlockApiController.php +++ b/system/Controllers/BlockApiController.php @@ -21,6 +21,12 @@ class BlockApiController extends ContentController $this->params = $request->getParams(); $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 */ if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); } @@ -30,6 +36,16 @@ class BlockApiController extends ContentController /* set item */ 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 $this->setPublishStatus(); @@ -77,7 +93,7 @@ class BlockApiController extends ContentController elseif(($this->params['block_id'] == 0) OR !isset($pageMarkdown[$this->params['block_id']])) { # 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 { @@ -201,6 +217,12 @@ class BlockApiController extends ContentController $this->params = $request->getParams(); $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 */ if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); } @@ -210,6 +232,16 @@ class BlockApiController extends ContentController /* set item */ 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 $this->setPublishStatus(); @@ -249,7 +281,7 @@ class BlockApiController extends ContentController if(!isset($pageMarkdown[$this->params['block_id']])) { # 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) { @@ -340,6 +372,12 @@ class BlockApiController extends ContentController $this->params = $request->getParams(); $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 # if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); } @@ -349,6 +387,16 @@ class BlockApiController extends ContentController # set item 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 $this->setPublishStatus(); @@ -382,7 +430,7 @@ class BlockApiController extends ContentController if(!isset($pageMarkdown[$oldIndex])) { # 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); @@ -432,6 +480,12 @@ class BlockApiController extends ContentController $this->params = $request->getParams(); $this->uri = $request->getUri()->withUserInfo(''); $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 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 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 $this->setPublishStatus(); @@ -616,7 +680,7 @@ class BlockApiController extends ContentController 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) @@ -632,7 +696,7 @@ class BlockApiController extends ContentController $allowedMimes = $this->getAllowedMtypes(); 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 @@ -653,7 +717,7 @@ class BlockApiController extends ContentController 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) @@ -681,7 +745,7 @@ class BlockApiController extends ContentController 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) @@ -692,7 +756,7 @@ class BlockApiController extends ContentController 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']); @@ -714,7 +778,7 @@ class BlockApiController extends ContentController 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(); @@ -725,7 +789,7 @@ class BlockApiController extends ContentController 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) @@ -803,7 +867,7 @@ class BlockApiController extends ContentController 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() @@ -828,6 +892,7 @@ class BlockApiController extends ContentController 'application/pdf', 'image/png', 'image/jpeg', + 'image/jpg', 'image/gif', 'image/svg+xml', 'font/*', diff --git a/system/Controllers/ContentBackendController.php b/system/Controllers/ContentBackendController.php index de15c06..2d8b24a 100644 --- a/system/Controllers/ContentBackendController.php +++ b/system/Controllers/ContentBackendController.php @@ -32,6 +32,9 @@ class ContentBackendController extends ContentController # set item 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) $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 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 $this->setPublishStatus(); @@ -153,7 +168,16 @@ class ContentBackendController extends ContentController 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) diff --git a/system/Controllers/ContentController.php b/system/Controllers/ContentController.php index 87dc47e..fe10875 100644 --- a/system/Controllers/ContentController.php +++ b/system/Controllers/ContentController.php @@ -10,6 +10,7 @@ use Typemill\Models\Folder; use Typemill\Models\Write; use Typemill\Models\WriteCache; use Typemill\Models\WriteYaml; +use Typemill\Models\WriteMeta; abstract class ContentController { @@ -51,6 +52,9 @@ abstract class ContentController # holds the content of the page protected $content; + + # holds the ownership (my content or not my content) + protected $mycontent = false; public function __construct(ContainerInterface $c) { @@ -432,4 +436,19 @@ abstract class ContentController $this->content = $content; 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; + } } \ No newline at end of file diff --git a/system/Controllers/MediaApiController.php b/system/Controllers/MediaApiController.php index 90caf6a..7c3470d 100644 --- a/system/Controllers/MediaApiController.php +++ b/system/Controllers/MediaApiController.php @@ -207,6 +207,12 @@ class MediaApiController extends ContentController $this->params = $request->getParams(); $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'])) { return $response->withJson(['errors' => 'image name is missing'],500); @@ -232,6 +238,12 @@ class MediaApiController extends ContentController $this->params = $request->getParams(); $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'])) { return $response->withJson(['errors' => 'file name is missing'],500); diff --git a/system/Controllers/MetaApiController.php b/system/Controllers/MetaApiController.php index d901169..05dd392 100644 --- a/system/Controllers/MetaApiController.php +++ b/system/Controllers/MetaApiController.php @@ -134,6 +134,12 @@ class MetaApiController extends ContentController $this->params = $request->getParams(); $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; $metaInput = isset($this->params['data']) ? $this->params['data'] : false ; $objectName = 'meta'; @@ -150,6 +156,16 @@ class MetaApiController extends ContentController # set item 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($this->item->elementType == "folder") { diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index 11501b8..09db70f 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -9,6 +9,8 @@ use Typemill\Models\Validation; use Typemill\Models\User; use Typemill\Models\ProcessFile; use Typemill\Models\ProcessImage; +use Typemill\Events\OnUserfieldsLoaded; +use Typemill\Events\OnSystemnaviLoaded; 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'; $users = $user->getUsers(); $route = $request->getAttribute('route'); + $navigation = $this->getNavigation(); - return $this->render($response, 'settings/system.twig', array('settings' => $settings, 'copyright' => $copyright, 'languages' => $languages, 'locale' => $locale, 'formats' => $defaultSettings['formats'] ,'users' => $users, 'route' => $route->getName() )); + # 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) { 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(); $defaultSettings = \Typemill\Settings::getDefaultSettings(); $params = $request->getParams(); @@ -232,9 +236,19 @@ class SettingsController extends Controller } /* add the users for navigation */ - $route = $request->getAttribute('route'); + $route = $request->getAttribute('route'); + $navigation = $this->getNavigation(); - return $this->render($response, 'settings/themes.twig', array('settings' => $userSettings, 'themes' => $themedata, 'route' => $route->getName() )); + # 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) @@ -300,8 +314,18 @@ class SettingsController extends Controller } $route = $request->getAttribute('route'); + $navigation = $this->getNavigation(); + + # set navigation active + $navigation['Plugins']['active'] = true; - return $this->render($response, 'settings/plugins.twig', array('settings' => $userSettings, 'plugins' => $plugins, 'route' => $route->getName() )); + return $this->render($response, 'settings/plugins.twig', array( + 'settings' => $userSettings, + 'acl' => $this->c->acl, + 'navigation' => $navigation, + 'plugins' => $plugins, + 'route' => $route->getName() + )); } /************************************* @@ -311,19 +335,7 @@ class SettingsController extends Controller public function saveThemes($request, $response, $args) { 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(); $params = $request->getParams(); $themeName = isset($params['theme']) ? $params['theme'] : false; @@ -410,18 +422,6 @@ class SettingsController extends Controller { 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(); $pluginSettings = array(); $userInput = $request->getParams(); @@ -481,6 +481,421 @@ class SettingsController extends Controller } } + /*********************** + ** USER MANAGEMENT ** + ***********************/ + + public function showAccount($request, $response, $args) + { + $username = $_SESSION['user']; + + $validate = new Validation(); + + if($validate->username($username)) + { + # get settings + $settings = $this->c->get('settings'); + + # 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 + )); + } + + $this->c->flash->addMessage('error', 'User does not exists'); + return $response->withRedirect($this->c->router->pathFor('home')); + } + + public function showUser($request, $response, $args) + { + # 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']] )); + } + + $validate = new Validation(); + + if($validate->username($args['username'])) + { + # get settings + $settings = $this->c->get('settings'); + + # get user with userdata + $user = new User(); + $userdata = $user->getSecureUser($args['username']); + + $username = $userdata['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'); + return $response->withRedirect($this->c->router->pathFor('user.account')); + } + + public function listUser($request, $response) + { + $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) + { + $userdata[] = $user->getUser($username); + } + + 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) + { + $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) + { + if($request->isPost()) + { + $params = $request->getParams(); + $user = new User(); + $validate = new Validation(); + $userroles = $this->c->acl->getRoles(); + + if($validate->newUser($params, $userroles)) + { + $userdata = array( + 'username' => $params['username'], + 'email' => $params['email'], + 'userrole' => $params['userrole'], + 'password' => $params['password']); + + $user->createUser($userdata); + + $this->c->flash->addMessage('info', 'Welcome, there is a new user!'); + return $response->withRedirect($this->c->router->pathFor('user.list')); + } + + $this->c->flash->addMessage('error', 'Please correct your input'); + return $response->withRedirect($this->c->router->pathFor('user.new')); + } + } + + public function updateUser($request, $response, $args) + { + + if($request->isPost()) + { + $params = $request->getParams(); + $userdata = $params['user']; + $user = new User(); + $validate = new Validation(); + $userroles = $this->c->acl->getRoles(); + + $redirectRoute = ($userdata['username'] == $_SESSION['user']) ? $this->c->router->pathFor('user.account') : $this->c->router->pathFor('user.show', ['username' => $userdata['username']]); + + # 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'] !== $userdata['username']) + { + return $response->withRedirect($this->c->router->pathFor('user.account')); + } + + # non admins cannot change their userrole, so set it to session-value + $userdata['userrole'] = $_SESSION['role']; + } + + # validate standard fields for users + if($validate->existingUser($userdata, $userroles)) + { + # 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); + $this->c->flash->addMessage('info', 'Saved all changes'); + return $response->withRedirect($redirectRoute); + } + elseif($validate->newPassword($userdata)) + { + $userdata['password'] = $userdata['newpassword']; + unset($userdata['newpassword']); + + $user->updateUser($userdata); + $this->c->flash->addMessage('info', 'Saved all changes'); + return $response->withRedirect($redirectRoute); + } + } + + $this->c->flash->addMessage('error', 'Please correct your input'); + return $response->withRedirect($redirectRoute); + } + } + + public function deleteUser($request, $response, $args) + { + if($request->isPost()) + { + $params = $request->getParams(); + $validate = new Validation(); + $user = new User(); + + # 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($_SESSION['user'] !== $params['username']) + { + return $response->withRedirect($this->c->router->pathFor('user.account')); + } + } + + if($validate->username($params['username'])) + { + $user->deleteUser($params['username']); + + # if user deleted his own account + if($_SESSION['user'] == $params['username']) + { + session_destroy(); + return $response->withRedirect($this->c->router->pathFor('auth.show')); + } + + $this->c->flash->addMessage('info', 'Say goodbye, the user is gone!'); + return $response->withRedirect($this->c->router->pathFor('user.list')); + } + + $this->c->flash->addMessage('error', 'Ups, we did not find that user'); + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']])); + } + } + + 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() + { + $themeFolder = $this->c->get('settings')['rootPath'] . $this->c->get('settings')['themeFolder']; + $themeFolderC = scandir($themeFolder); + $themes = array(); + foreach ($themeFolderC as $key => $theme) + { + if (!in_array($theme, array(".",".."))) + { + if (is_dir($themeFolder . DIRECTORY_SEPARATOR . $theme)) + { + $themes[] = $theme; + } + } + } + return $themes; + } + + private function getCopyright() + { + return array( + "©", + "CC-BY", + "CC-BY-NC", + "CC-BY-NC-ND", + "CC-BY-NC-SA", + "CC-BY-ND", + "CC-BY-SA", + "None" + ); + } + + private function getLanguages() + { + return array( + 'en' => 'English', + 'ru' => 'Russian', + 'nl' => 'Dutch, Flemish', + 'de' => 'German', + 'it' => 'Italian', + '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) @@ -543,7 +958,7 @@ class SettingsController extends Controller $imageFieldDefinitions[$fieldName] = $fieldDefinition; } } - if(!$fieldDefinition && $fieldName != 'active') + if(!$fieldDefinition && $objectType != 'users' && $fieldName != 'active') { $_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!'); } @@ -588,391 +1003,5 @@ class SettingsController extends Controller } } return $userInput; - } - - /*********************** - ** USER MANAGEMENT ** - ***********************/ - - public function showAccount($request, $response, $args) - { - $username = $_SESSION['user']; - - $validate = new Validation(); - - if($validate->username($username)) - { - # get settings - $settings = $this->c->get('settings'); - - # get user with userdata - $user = new User(); - $userdata = $user->getSecureUser($username); - - # instantiate field-builder - $fieldsModel = new Fields(); - - # get the field-definitions - $fieldDefinitions = $this->getUserFields($_SESSION['role']); - - # prepare userdata for field-builder - $userSettings['user'][$username] = $userdata; - - # generate the input form - $userform = $fieldsModel->getFields($userSettings, 'user', $username, $fieldDefinitions); - - $route = $request->getAttribute('route'); - - return $this->render($response, 'settings/user.twig', array( - 'settings' => $settings, - '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 $this->render($response, 'settings/user.twig', array('settings' => $settings, 'usersettings' => $userSettings, 'userdata' => $userdata, 'userrole' => false, 'username' => $username, 'userform' => $userform, 'route' => $route->getName() )); - } - - $this->c->flash->addMessage('error', 'User does not exists'); - return $response->withRedirect($this->c->router->pathFor('home')); - } - - public function showUser($request, $response, $args) - { - # if user has no rights to watch userlist, then only show his user-entry - 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']] )); - } - - $validate = new Validation(); - - if($validate->username($args['username'])) - { - # get settings - $settings = $this->c->get('settings'); - - # get user with userdata - $user = new User(); - $userdata = $user->getSecureUser($args['username']); - - $username = $userdata['username']; - - # instantiate field-builder - $fieldsModel = new Fields(); - - # get the field-definitions - $fieldDefinitions = $this->getUserFields($userdata['userrole']); - - # prepare userdata for field-builder - $userSettings['user'][$username] = $userdata; - - # generate the input form - $userform = $fieldsModel->getFields($userSettings, 'user', $username, $fieldDefinitions); - - $route = $request->getAttribute('route'); - - return $this->render($response, 'settings/user.twig', array( - 'settings' => $settings, - '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'); - return $response->withRedirect($this->c->router->pathFor('user.account')); - } - - private function getUserFields($role) - { - $fields = []; - $fields['username'] = ['label' => 'Username (read only)', 'type' => 'text', 'readonly' => true]; - $fields['image'] = ['label' => 'Profile-Image', 'type' => 'image']; - $fields['description'] = ['label' => 'Author-Description (Markdown)', 'type' => 'textarea']; - $fields['firstname'] = ['label' => 'First Name', 'type' => 'text']; - $fields['lastname'] = ['label' => 'Last Name', 'type' => 'text']; - $fields['email'] = ['label' => 'E-Mail', 'type' => 'text', 'required' => true]; - $fields['userrole'] = ['label' => 'Role', 'type' => 'text', 'readonly' => true]; - $fields['password'] = ['label' => 'Actual Password', 'type' => 'password']; - $fields['newpassword'] = ['label' => 'New Password', 'type' => 'password']; - - # dispatch fields; - - # change admin stuff - if($_SESSION['role'] == 'administrator') - { - $userroles = $this->c->acl->getRoles(); - $options = []; - - # we need associative array to make select-field with key/value work - foreach($userroles as $userrole) - { - $options[$userrole] = $userrole; - } - - $fields['userrole'] = ['label' => 'Role', 'type' => 'select', 'options' => $options]; - } - - $userform = []; - $userform['forms']['fields'] = $fields; - return $userform; - } - - public function listUser($request, $response) - { - $user = new User(); - $users = $user->getUsers(); - $userdata = array(); - $route = $request->getAttribute('route'); - $settings = $this->c->get('settings'); - - foreach($users as $username) - { - $userdata[] = $user->getUser($username); - } - - return $this->render($response, 'settings/userlist.twig', array('settings' => $settings, 'users' => $users, 'userdata' => $userdata, 'route' => $route->getName() )); - } - - 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() )); - } - - public function createUser($request, $response, $args) - { - 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(); - $user = new User(); - $validate = new Validation(); - $userroles = $this->c->acl->getRoles(); - - 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']); - - $user->createUser($userdata); - - $this->c->flash->addMessage('info', 'Welcome, there is a new user!'); - return $response->withRedirect($this->c->router->pathFor('user.list')); - } - - $this->c->flash->addMessage('error', 'Please correct your input'); - return $response->withRedirect($this->c->router->pathFor('user.new')); - } - } - - public function updateUser($request, $response, $args) - { - - 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(); - $userdata = $params['user']; - $user = new User(); - $validate = new Validation(); - $userroles = $this->c->acl->getRoles(); - - print_r($params); - die(); - - # check if user is allowed to view (edit) userlist and other users - if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view')) - { - # if an editor tries to update other userdata than its own */ - if($_SESSION['user'] !== $userdata['username']) - { - return $response->withRedirect($this->c->router->pathFor('user.account')); - } - - # non admins cannot change his userrole, so set it to session-value - $userdata['userrole'] = $_SESSION['role']; - } - - # validate standard fields for users - if($validate->existingUser($userdata, $userroles)) - { - # validate custom input fields and return images - $userfields = $this->getUserFields($userdata['userrole']); - $imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields); - - if(!isset($_SESSION['errors']) && !empty($imageFields)) - { - $images = $request->getUploadedFiles(); - - if(isset($images['user'])) - { - # set image size - $settings = $this->c->get('settings'); - $settings['images']['live'] = ['width' => 500, 'height' => 500]; - $userdata = $this->saveImages($imageFields, $userdata, $settings, $images['user']); - } - } - - # 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($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); - } - - if(empty($userdata['password']) AND empty($userdata['newpassword'])) - { - $user->updateUser($userdata); - $this->c->flash->addMessage('info', 'Saved all changes'); - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); - } - elseif($validate->newPassword($userdata)) - { - $userdata['password'] = $userdata['newpassword']; - unset($userdata['newpassword']); - - $user->updateUser($userdata); - $this->c->flash->addMessage('info', 'Saved all changes'); - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); - } - } - - $this->c->flash->addMessage('error', 'Please correct your input'); - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); - } - } - - public function deleteUser($request, $response, $args) - { - 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(); - $validate = new Validation(); - $user = new User(); - - /* non admins have different update rights */ - if($_SESSION['role'] !== 'administrator') - { - /* if an editor tries to delete other user than its own */ - if($_SESSION['user'] !== $params['username']) - { - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] )); - } - } - - if($validate->username($params['username'])) - { - $user->deleteUser($params['username']); - - # if user deleted his own account - if($_SESSION['user'] == $params['username']) - { - session_destroy(); - return $response->withRedirect($this->c->router->pathFor('auth.show')); - } - - $this->c->flash->addMessage('info', 'Say goodbye, the user is gone!'); - return $response->withRedirect($this->c->router->pathFor('user.list')); - } - - $this->c->flash->addMessage('error', 'Ups, we did not find that user'); - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']])); - } - } - - private function getThemes() - { - $themeFolder = $this->c->get('settings')['rootPath'] . $this->c->get('settings')['themeFolder']; - $themeFolderC = scandir($themeFolder); - $themes = array(); - foreach ($themeFolderC as $key => $theme) - { - if (!in_array($theme, array(".",".."))) - { - if (is_dir($themeFolder . DIRECTORY_SEPARATOR . $theme)) - { - $themes[] = $theme; - } - } - } - return $themes; - } - - private function getCopyright() - { - return array( - "©", - "CC-BY", - "CC-BY-NC", - "CC-BY-NC-ND", - "CC-BY-NC-SA", - "CC-BY-ND", - "CC-BY-SA", - "None" - ); - } - - private function getLanguages() - { - return array( - 'en' => 'English', - 'ru' => 'Russian', - 'nl' => 'Dutch, Flemish', - 'de' => 'German', - 'it' => 'Italian', - 'fr' => 'French', - ); - } + } } diff --git a/system/Controllers/SetupController.php b/system/Controllers/SetupController.php index bb764b3..5dd0564 100644 --- a/system/Controllers/SetupController.php +++ b/system/Controllers/SetupController.php @@ -66,11 +66,11 @@ class SetupController extends Controller $validate = new Validation(); $user = new User(); - /* set user as admin */ + # set user as admin $params['userrole'] = 'administrator'; - /* get userroles for validation */ - $userroles = $user->getUserroles(); + # get userroles for validation + $userroles = $this->c->acl->getRoles(); /* validate user */ if($validate->newUser($params, $userroles)) diff --git a/system/Events/OnSystemnaviLoaded.php b/system/Events/OnSystemnaviLoaded.php new file mode 100644 index 0000000..3f8f278 --- /dev/null +++ b/system/Events/OnSystemnaviLoaded.php @@ -0,0 +1,14 @@ +getUser($params['username']); + # make sure passwords are not overwritten + if(isset($params['newpassword'])){ unset($params['newpassword']); } 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); $this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update); - $_SESSION['user'] = $update['username']; - $_SESSION['role'] = $update['userrole']; + # if user updated his own profile, update session data + if($_SESSION['user'] == $params['username']) + { + $_SESSION['role'] = $update['userrole']; - if(isset($update['firstname'])) - { - $_SESSION['firstname'] = $update['firstname']; - } - if(isset($update['lastname'])) - { - $_SESSION['lastname'] = $update['lastname']; + if(isset($update['firstname'])) + { + $_SESSION['firstname'] = $update['firstname']; + } + if(isset($update['lastname'])) + { + $_SESSION['lastname'] = $update['lastname']; + } } return $userdata['username']; diff --git a/system/Models/WriteMeta.php b/system/Models/WriteMeta.php index f04d7af..e92b8b1 100644 --- a/system/Models/WriteMeta.php +++ b/system/Models/WriteMeta.php @@ -52,11 +52,18 @@ class WriteMeta extends WriteYaml $description = $this->generateDescription($content, $parsedown, $item); + # owner holds the edit-rights + $owner = ''; + if(isset($_SESSION['user'])) + { + $owner = $_SESSION['user']; + } + $author = $settings['author']; 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']; } @@ -71,6 +78,7 @@ class WriteMeta extends WriteYaml 'meta' => [ 'title' => $title, 'description' => $description, + 'owner' => $owner, 'author' => $author, 'created' => date("Y-m-d"), '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 public function getPageMetaBlank($content, $settings, $item) { + # owner holds the edit-rights + $owner = ''; + if(isset($_SESSION['user'])) + { + $owner = $_SESSION['user']; + } + $author = $settings['author']; if(isset($_SESSION)) @@ -107,6 +122,7 @@ class WriteMeta extends WriteYaml 'meta' => [ 'title' => '', 'description' => '', + 'owner' => $owner, 'author' => $author, 'created' => date("Y-m-d"), 'time' => date("H-i-s"), diff --git a/system/Routes/Web.php b/system/Routes/Web.php index 713e7ae..d129660 100644 --- a/system/Routes/Web.php +++ b/system/Routes/Web.php @@ -36,20 +36,22 @@ $app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->a $app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings'])); $app->get('/tm/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new accessController($container['router'], $container['acl'], 'settings', 'view')); -$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new accessController($container['router'], $container['acl'], 'settings', 'update')); -$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new accessController($container['router'], $container['acl'], 'themes', 'view')); -$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new accessController($container['router'], $container['acl'], 'themes', 'update')); +$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new accessController($container['router'], $container['acl'], 'system', 'view')); +$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new accessController($container['router'], $container['acl'], 'system', 'update')); -$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new accessController($container['router'], $container['acl'], 'plugins', 'view')); -$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new accessController($container['router'], $container['acl'], 'plugins', 'update')); -$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new accessController($container['router'], $container['acl'], 'users', 'create')); +$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new accessController($container['router'], $container['acl'], 'system', 'view')); +$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new accessController($container['router'], $container['acl'], 'system', 'update')); + +$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new accessController($container['router'], $container['acl'], 'system', 'view')); +$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new accessController($container['router'], $container['acl'], 'system', 'update')); + +$app->get('/tm/account', SettingsController::class . ':showAccount')->setName('user.account')->add(new accessController($container['router'], $container['acl'], 'user', 'view')); +$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new accessController($container['router'], $container['acl'], 'user', 'create')); $app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new accessController($container['router'], $container['acl'], 'user', 'create')); $app->post('/tm/user/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/account', SettingsController::class . ':showAccount')->setName('user.account')->add(new accessController($container['router'], $container['acl'], 'user', 'view')); $app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new accessController($container['router'], $container['acl'], 'user', 'view')); -$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new accessController($container['router'], $container['acl'], 'userlist', 'view')); +$app->get('/tm/users', SettingsController::class . ':listUser')->setName('user.list')->add(new accessController($container['router'], $container['acl'], 'userlist', 'view')); $app->get('/tm/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new accessController($container['router'], $container['acl'], 'content', 'view')); $app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new accessController($container['router'], $container['acl'], 'content', 'view')); diff --git a/system/Settings.php b/system/Settings.php index 1e8eb16..cc29259 100644 --- a/system/Settings.php +++ b/system/Settings.php @@ -190,32 +190,29 @@ class Settings public static function loadResources() { return ['content', + 'mycontent', 'user', 'userlist', - 'settings', - 'themes', - 'plugins']; + 'system']; } public static function loadRolesAndPermissions() { - $guest['name'] = 'guest'; - $guest['inherits'] = NULL; - $guest['permissions'] = []; - $member['name'] = 'member'; - $member['inherits'] = 'guest'; + $member['inherits'] = NULL; $member['permissions'] = ['user' => ['view','update','delete']]; $author['name'] = 'author'; $author['inherits'] = 'member'; - $author['permissions'] = ['content' => ['view','create', 'update', 'delete']]; + $author['permissions'] = ['mycontent' => ['view', 'create', 'update'], + 'content' => ['view']]; $editor['name'] = 'editor'; $editor['inherits'] = 'author'; - $editor['permissions'] = ['content' => ['publish', 'depublish']]; + $editor['permissions'] = [ 'mycontent' => ['delete', 'publish', 'unpublish'], + 'content' => ['create', 'update', 'delete', 'publish', 'unpublish']]; - return [$guest, $member, $author, $editor]; + return [$member, $author, $editor]; } public static function createAcl($roles, $resources) @@ -235,7 +232,8 @@ class Settings foreach($roles as $role) { $acl->addRole(new Role($role['name']), $role['inherits']); - foreach($role['permissions'] as $resource => $permissions) + + foreach($role['permissions'] as $resource => $permissions) { $acl->allow($role['name'], $resource, $permissions); } diff --git a/system/Translations.php b/system/Translations.php index 7d010be..3bd1abf 100644 --- a/system/Translations.php +++ b/system/Translations.php @@ -9,7 +9,7 @@ class Translations $yaml = new Models\WriteYaml(); $settings = $yaml->getYaml('settings', 'settings.yaml'); - if($settings === FALSE){ + if(!isset($settings['language'])){ $language = \Typemill\Settings::whichLanguage(); } else { $language = $settings['language']; diff --git a/system/author/css/style.css b/system/author/css/style.css index 8101f69..7911176 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -720,6 +720,9 @@ li.row{ width: 100%; box-sizing: border-box; } +li.row.header{ + display:none; +} li.row ul{ background: #f9f8f6; margin: 5px 0; @@ -738,6 +741,15 @@ li.col.username, li.col.email, li.col.userrole, li.col.edit{ li.col.username{ 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{ width: 100%; } @@ -2781,6 +2793,23 @@ footer a:focus, footer a:hover, footer a:active margin-top: 10px; 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{ margin: 0px; } @@ -2789,10 +2818,10 @@ footer a:focus, footer a:hover, footer a:active } .col.edit{ width: 10%; - } + } li.col.username{ border-top: 0px; - border-left: 2px solid #70c1b3; + border-left: 0px solid #70c1b3; } .buttonset{ width: 76%; diff --git a/system/author/editor/editor-blox.twig b/system/author/editor/editor-blox.twig index fb48f19..5a037d5 100644 --- a/system/author/editor/editor-blox.twig +++ b/system/author/editor/editor-blox.twig @@ -5,6 +5,8 @@