From ea5e5975f1b97d1871f8d349ddb043a97b316612 Mon Sep 17 00:00:00 2001 From: Jon Mifsud Date: Fri, 20 Feb 2015 08:29:27 +0100 Subject: [PATCH 1/8] update for symphony 2.5 compatibility --- content/content.index.php | 2 +- extension.driver.php | 18 +++++++++--------- extension.meta.xml | 5 ++++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/content/content.index.php b/content/content.index.php index 71b033f..ba20055 100644 --- a/content/content.index.php +++ b/content/content.index.php @@ -21,7 +21,7 @@ function __construct(){ */ public function build($context) { - if(Administration::instance()->Author->isDeveloper()) { + if(Symphony::Author()->isDeveloper()) { if($_POST['with-selected'] == 'delete' && is_array($_POST['items'])) { foreach($_POST['items'] as $id_role => $value) diff --git a/extension.driver.php b/extension.driver.php index b521cba..eaf89c0 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -79,7 +79,7 @@ public function fetchNavigation() { public function extendNavigation($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Administration::instance()->Author->isDeveloper()) { + if($data == false || Symphony::Author()->isDeveloper()) { return; } @@ -153,7 +153,7 @@ public function deleteAuthorRole($context) { * The context, providing the form and the author object */ public function addRolePicker($context) { - if(Administration::instance()->Author->isDeveloper()) { + if(Symphony::Author()->isDeveloper()) { $group = new XMLElement('fieldset'); $group->setAttribute('class', 'settings'); $group->appendChild(new XMLElement('legend', __('Author Role'))); @@ -232,7 +232,7 @@ public function checkCallback($context) { private function adjustIndex($context, $callback) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Administration::instance()->Author->isDeveloper()) { + if($data == false || Symphony::Author()->isDeveloper()) { return; } @@ -266,7 +266,7 @@ private function adjustIndex($context, $callback) { if($rules['own_entries'] == 1 || $rules['edit'] == 0 || $rules['delete'] == 0 || $rules['use_filter'] == 1) { // For only show entries created by this author: // Get a list of entry id's created by this author: - $id_author = Administration::instance()->Author->get('id'); + $id_author = Symphony::Author()->get('id'); if($rules['own_entries'] == 1) { // Only get the ID's of the current author to begin with: @@ -428,7 +428,7 @@ private function adjustIndex($context, $callback) { public function makePreAdjustements($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Administration::instance()->Author->isDeveloper()) { + if($data == false || Symphony::Author()->isDeveloper()) { return; } @@ -493,7 +493,7 @@ private static function replaceChild($parent, $child) { private function adjustEntryEditor($context, $callback) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Administration::instance()->Author->isDeveloper()) { + if($data == false || Symphony::Author()->isDeveloper()) { return; } @@ -580,7 +580,7 @@ private function adjustEntryEditor($context, $callback) { */ private function getCurrentAuthorRoleData() { if(Administration::instance()->isLoggedIn()) { - $id_author = Administration::instance()->Author->get('id'); + $id_author = Symphony::Author()->get('id'); $id_role = $this->getAuthorRole($id_author); if($id_role != false) { @@ -601,7 +601,7 @@ private function getCurrentAuthorRoleData() { public function modifyAreas($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Administration::instance()->Author->isDeveloper()) { + if($data == false || Symphony::Author()->isDeveloper()) { return; } @@ -622,7 +622,7 @@ public function modifyAreas($context) { * The context */ public function saveAuthorRole($context) { - if(Administration::instance()->Author->isDeveloper()) { + if(Symphony::Author()->isDeveloper()) { $id_role = intval($_POST['fields']['role']); $id_author = $context['author']->get('id'); diff --git a/extension.meta.xml b/extension.meta.xml index 0e2920b..afafe41 100644 --- a/extension.meta.xml +++ b/extension.meta.xml @@ -15,11 +15,14 @@ - + Added functionality to hide menu elements created by other extensions. __Important:__ Since extensions can not (yet) influence the execution order of extensions in Symphony, you need to apply a tiny little 'hack' to Symphony to make sure the 'Author Roles'-extension is executed as last, after all other extensions have made their possible modifications to the navigation. Read the readme for more details. + + Updated compatibility for Symphony 2.5 + From 168c454cb8cde8c108e63903d6eaf9f27da1a8b0 Mon Sep 17 00:00:00 2001 From: Jon Mifsud Date: Fri, 12 Feb 2016 16:26:36 +0100 Subject: [PATCH 2/8] fix author issues with create new and restricted access errors --- extension.driver.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/extension.driver.php b/extension.driver.php index eaf89c0..7e4e059 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -253,12 +253,13 @@ private function adjustIndex($context, $callback) { // Check if there are rules: if($rules['create'] == 0) { // It is not allowed to create new items: - $children = current($context['oPage']->Context->getChildrenByName('ul'))->getChildrenByName('li'); + $topMenu = current($context['oPage']->Context->getChildrenByName('ul')); + $children = $topMenu->getChildrenByName('li'); foreach($children as $key => $child) { - if(strpos($child->getValue(),__('Create New')) !== false) { - $value = $child->getValue(); - $child->setValue(''.strip_tags(str_replace(__('Create New'), '', $value)).''); + var_dump($child->getValue()->getValue()); + if(strpos($child->getValue()->getValue(),__('Create New')) !== false) { + $topMenu->removeChildAt($key); } } } @@ -456,7 +457,11 @@ private static function findChildren($element, $names) { $children = array(); - foreach($element->getChildren() as $child) { + if (!is_object($element)){ + return $children; + } + + foreach($element->getIterator() as $child) { $children = array_merge($children, self::findChildren($child,$names)); if(in_array($child->getName(), $names )) { @@ -471,7 +476,7 @@ private static function findChildren($element, $names) { // name matches the name of the second argument with // the second argument private static function replaceChild($parent, $child) { - foreach($parent->getChildren() as $position => $oldChild) { + foreach($parent->getIterator() as $position => $oldChild) { if($oldChild->getName() == $child->getName()) { $parent->replaceChildAt($position,$child); From 52f51000e76a68dff1cf69340bc63327ed0778db Mon Sep 17 00:00:00 2001 From: Jon Mifsud Date: Tue, 16 Feb 2016 11:14:30 +0100 Subject: [PATCH 3/8] update in preparation for v2.0 --- extension.driver.php | 607 ++++++++++++++++++++++++++++--------------- extension.meta.xml | 12 + readme.md | 11 +- 3 files changed, 422 insertions(+), 208 deletions(-) diff --git a/extension.driver.php b/extension.driver.php index 7e4e059..88a29bb 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -1,19 +1,6 @@ 'Author Roles', - 'version' => '1.2', - 'release-date' => '2012-10-04', - 'author' => array( - 'name' => 'Twisted Interactive', - 'website' => 'http://www.twisted.nl'), - 'description' => 'Author Roles for Symphony 2.2 and above' - ); - } // Set the delegates: public function getSubscribedDelegates() @@ -49,19 +36,412 @@ public function getSubscribedDelegates() 'delegate' => 'AdminPagePreGenerate', 'callback' => 'checkCallback' ), - array( - 'page' => '/backend/', - 'delegate' => 'InitaliseAdminPageHead', - 'callback' => 'makePreAdjustements' - ), array( 'page' => '/system/authors/', 'delegate' => 'AddDefaultAuthorAreas', 'callback' => 'modifyAreas' - ) + ), + array( + 'page' => '/publish/', + 'delegate' => 'AdjustPublishFiltering', + 'callback' => 'adjustPublishFiltering' + ), + array( + 'page' => '/publish/edit/', + 'delegate' => 'EntryPreRender', + 'callback' => 'entryPreRender' + ), + array( + 'page' => '/publish/edit/', + 'delegate' => 'EntryPreEdit', + 'callback' => 'entryPreEdit' + ), + array( + 'page' => '/publish/new/', + 'delegate' => 'EntryPreCreate', + 'callback' => 'entryPreCreate' + ), + array( + 'page' => '/publish/', + 'delegate' => 'EntryPreDelete', + 'callback' => 'entryPreDelete' + ), ); } + /** + * Check if the current user is the Author of the entry + * @param $entry - the entry to be checked + * @param $rules - the rules applying to this section + * @return boolean + */ + private function isOwnEntry($entry,$rules){ + $sectionId = $entry->get('section_id'); + + $authorId = Symphony::Author()->get('id'); + $authorName = Symphony::Author()->get('first_name') . ' ' . Symphony::Author()->get('last_name'); + + $canAccess = true; + + $fieldId = FieldManager::fetchFieldIDFromElementName('authors',$sectionId); + // $field = FieldManager::fetch($fieldId); + + $fieldType = FieldManager::fetchFieldTypeFromID($fieldId); + + if ($field){ + + $fieldData = $entry->getData($fieldId); + + if ($fieldType == 'author'){ + //use symphony author + $canAccess = ($fieldData['author_id'] == $authorId); + } else { + $fieldValue = $fieldData['value']; + if (!is_array($fieldValue)){ + $fieldValue = array($fieldValue); + } + $fieldValue = array_map('strtolower', $fieldValue); + + if ( in_array(strtolower($authorName), $fieldValue)){ + $canAccess = true; + } else { + $canAccess = false; + } + } + } else { + //use symphony author + $canAccess = ($entry->get('author_id') == $authorId); + } + + return $canAccess; + } + + /** + * Check if the rules permit or deny access to the entry id + * @param $entryId - the entry id to be checked + * @param $rules - the rules applying to this section + * @return boolean + */ + private function filterCanAccess($entryId,$rules){ + $rule = explode(':', $rules['filter_rule']); + + $canAccess = true; + + if(count($rule) == 2) { + // Valid filter, now get the ID's: + $filteredIDs = array(); + $action = $rule[0]; + $idsArr = explode(',', $rule[1]); + + foreach($idsArr as $idExpr) { + $a = explode('-', trim($idExpr)); + + if(count($a)==1) { + // Regular ID + $filteredIDs[] = $a[0]; + } + elseif(count($a)==2) { + // Range + $from = $a[0]; + $to = $a[1]; + + if($to >= $from) { + for($i = $from; $i <= $to; $i++){ + $filteredIDs[] = $i; + } + } + } + } + + switch($action) { + case 'show' : + // Only show the given ids. + if ( in_array($entryId, $filteredIDs)){ + $canAccess = true; + } else { + $canAccess = false; + } + break; + case 'hide' : + // Only show entries which do not have the given ids. + if ( !in_array($entryId, $filteredIDs)){ + $canAccess = true; + } else { + $canAccess = false; + } + break; + } + } + + return $canAccess; + } + + /** + * Check that the current user has access prior to deleting an entry + */ + public function entryPreDelete($context) { + $data = $this->getCurrentAuthorRoleData(); + + if($data == false || Symphony::Author()->isDeveloper()) { + return; + } + + $section = Symphony::Engine()->getPageCallback()['context']['section_handle']; + + foreach($data['sections'] as $id_section => $rules) { + if($rules['handle'] == $section) { + + $canAccess = true; + + if($rules['delete'] == 0) { + $canAccess = false; + } + + if ( !$canAccess ){ + //throw custom error if access to entry is not allowed + Administration::instance()->throwCustomError( + __('Restricted Access'), + __('You are not allowed to delete entries in this section'), + Page::HTTP_STATUS_NOT_FOUND + ); + } + } + } + } + + /** + * Check that the current user has access prior to showing creating an entry + */ + public function entryPreCreate($context) { + + $data = $this->getCurrentAuthorRoleData(); + + if($data == false || Symphony::Author()->isDeveloper()) { + return; + } + + $section = Symphony::Engine()->getPageCallback()['context']['section_handle']; + + foreach($data['sections'] as $id_section => $rules) { + if($rules['handle'] == $section) { + + $canAccess = true; + + if($rules['create'] == 0) { + $canAccess = false; + } + + if ( !$canAccess ){ + //throw custom error if access to entry is not allowed + Administration::instance()->throwCustomError( + __('Restricted Access'), + __('You are not allowed to create entries in this section'), + Page::HTTP_STATUS_NOT_FOUND + ); + } + } + + } + } + + /** + * Check that the current user has access prior to saving entry edits + */ + public function entryPreEdit($context) { + + $data = $this->getCurrentAuthorRoleData(); + + if($data == false || Symphony::Author()->isDeveloper()) { + return; + } + + $section = Symphony::Engine()->getPageCallback()['context']['section_handle']; + + foreach($data['sections'] as $id_section => $rules) { + if($rules['handle'] == $section) { + + $canAccess = true; + + if($rules['edit'] == 0) { + $canAccess = false; + } + + if($rules['own_entries'] == 1 && $canAccess) { + $canAccess = $this->isOwnEntry(); + } + + // Add or remove ID's from the filter: + if($rules['use_filter'] == 1 && $canAccess) { + $canAccess = $this->filterCanAccess($context['entry']->get('id'),$rules); + } + + if ( !$canAccess ){ + //throw custom error if access to entry is not allowed + Administration::instance()->throwCustomError( + __('Restricted Access'), + __('You do not have permissions to edit entry %s.', array($context['entry']->get('id'))), + Page::HTTP_STATUS_NOT_FOUND + ); + } + } + + } + } + + /** + * Check that the current user has view or edit access prior to showing the entry edit screen + */ + public function entryPreRender($context) { + + $data = $this->getCurrentAuthorRoleData(); + + if($data == false || Symphony::Author()->isDeveloper()) { + return; + } + + $section = Symphony::Engine()->getPageCallback()['context']['section_handle']; + + foreach($data['sections'] as $id_section => $rules) { + if($rules['handle'] == $section) { + + $canAccess = true; + + if($rules['visible'] == 0 && $rules['edit'] == 0) { + $canAccess = false; + } + + if($rules['own_entries'] == 1 && $canAccess) { + $canAccess = $this->isOwnEntry(); + } + + // Add or remove ID's from the filter: + if($rules['use_filter'] == 1 && $canAccess) { + $canAccess = $this->filterCanAccess($context['entry']->get('id'),$rules); + } + + if ( !$canAccess ){ + //throw custom error if access to entry is not allowed + Administration::instance()->throwCustomError( + __('Unknown Entry'), + __('The Entry, %s, could not be found.', array($context['entry']->get('id'))), + Page::HTTP_STATUS_NOT_FOUND + ); + } + } + + } + // var_dump($context);die; + } + + /** + * Ensure that any required filtering is done to show only entries which the user has access to in the list views + */ + public function adjustPublishFiltering($context) { + // var_dump($context);die; + + $authorId = Symphony::Author()->get('id'); + $authorName = Symphony::Author()->get('first_name') . ' ' . Symphony::Author()->get('last_name'); + + $data = $this->getCurrentAuthorRoleData(); + + if($data == false || Symphony::Author()->isDeveloper()) { + return; + } + + $section = Symphony::Engine()->getPageCallback()['context']['section_handle']; + + foreach($data['sections'] as $id_section => $rules) { + if($rules['handle'] == $section) { + + if($rules['visible'] == 0) { + Administration::instance()->throwCustomError( + __('Unknown Section'), + __('The Section, %s, could not be found.', array($section)), + Page::HTTP_STATUS_NOT_FOUND + ); + } + + if($rules['own_entries'] == 1) { + // Only get the ID's of the current author to begin with: + + //if section has field Author -- defined by config + // $sectionObject = SectionManager::fetch($id_section); + + $fieldId = FieldManager::fetchFieldIDFromElementName('authors',$id_section); + $field = FieldManager::fetch($fieldId); + + $fieldType = FieldManager::fetchFieldTypeFromID($fieldId); + + if ($field){ + $context['joins'] .= " LEFT JOIN `tbl_entries_data_{$fieldId}` as `a{$fieldId}` on `e`.`id` = `a{$fieldId}`.`entry_id` "; + if ($fieldType == 'author'){ + //use symphony author + $context['where'] .= " AND `a{$fieldId}`.`author_id` = '{$authorId}'"; + } else { + $context['where'] .= " AND `a{$fieldId}`.`value` = '{$authorName}'"; + } + } else { + //use symphony author + $context['where'] .= " AND `e`.`author_id` = '{$authorId}'"; + } + // $results = Symphony::Database()->fetch('SELECT `id` FROM `tbl_entries` WHERE `author_id`'); + } + + + // Add or remove ID's from the filter: + if($rules['use_filter'] == 1) { + $rule = explode(':', $rules['filter_rule']); + + if(count($rule) == 2) { + // Valid filter, now get the ID's: + $filteredIDs = array(); + $action = $rule[0]; + $idsArr = explode(',', $rule[1]); + + foreach($idsArr as $idExpr) { + $a = explode('-', trim($idExpr)); + + if(count($a)==1) { + // Regular ID + $filteredIDs[] = $a[0]; + } + elseif(count($a)==2) { + // Range + $from = $a[0]; + $to = $a[1]; + + if($to >= $from) { + for($i = $from; $i <= $to; $i++){ + $filteredIDs[] = $i; + } + } + } + } + + $filteredIDs = MySQL::cleanValue(implode(',',$filteredIDs)); + + switch($action) { + case 'show' : + // Only show the given ids. + $context['where'] .= " AND `e`.`id` IN ('{$filteredIDs}')"; + break; + case 'hide' : + // Only show entries which do not have the given ids. + $context['where'] .= " AND `e`.`id` NOT IN ('{$filteredIDs}')"; + break; + } + } + } + + + + } + } + } + + /** + * Add Author Roles Navigation Items + */ public function fetchNavigation() { return array( array( @@ -230,6 +610,8 @@ public function checkCallback($context) { * @return mixed */ private function adjustIndex($context, $callback) { + + $data = $this->getCurrentAuthorRoleData(); if($data == false || Symphony::Author()->isDeveloper()) { @@ -263,187 +645,6 @@ private function adjustIndex($context, $callback) { } } } - - if($rules['own_entries'] == 1 || $rules['edit'] == 0 || $rules['delete'] == 0 || $rules['use_filter'] == 1) { - // For only show entries created by this author: - // Get a list of entry id's created by this author: - $id_author = Symphony::Author()->get('id'); - - if($rules['own_entries'] == 1) { - // Only get the ID's of the current author to begin with: - $results = Symphony::Database()->fetch('SELECT `id` FROM `tbl_entries` WHERE `author_id` = '.$id_author.';'); - } - else { - // Get all the ID's: - $results = Symphony::Database()->fetch('SELECT `id` FROM `tbl_entries`;'); - } - - $ids = array(); - - foreach($results as $result) - { - $ids[] = $result['id']; - } - - // Add or remove ID's from the filter: - if($rules['use_filter'] == 1) { - $rule = explode(':', $rules['filter_rule']); - - if(count($rule) == 2) { - // Valid filter, now get the ID's: - $filteredIDs = array(); - $action = $rule[0]; - $idsArr = explode(',', $rule[1]); - - foreach($idsArr as $idExpr) { - $a = explode('-', trim($idExpr)); - - if(count($a)==1) { - // Regular ID - $filteredIDs[] = $a[0]; - } - elseif(count($a)==2) { - // Range - $from = $a[0]; - $to = $a[1]; - - if($to >= $from) { - for($i = $from; $i <= $to; $i++){ - $filteredIDs[] = $i; - } - } - } - } - - switch($action) { - case 'show' : - // Only show the given ids. Well that's easy: - $ids = $filteredIDs; - break; - case 'hide' : - // Remove the filtered ids from the ids-array: - $ids = array_diff($ids, $filteredIDs); - break; - } - } - } - - // Now, check each table row: - $newContents = new XMLElement('div', null, $context['oPage']->Contents->getAttributes()); - - foreach($context['oPage']->Contents->getChildren() as $contentsChild) { - if($contentsChild->getName() == 'form') { - $newForm = new XMLElement('form', null, $contentsChild->getAttributes()); - - foreach($contentsChild->getChildren() as $formChild) { - // only show entries created by this author, or which are allowed by the filter: - if($formChild->getName() == 'table' && ($rules['own_entries'] == 1 || $rules['use_filter'] == 1)) { - $newTable = new XMLElement('table', null, $formChild->getAttributes()); - - foreach($formChild->getChildren() as $tableChild) { - if($tableChild->getName() == 'tbody') { - $newTableBody = new XMLElement('tbody', null, $tableChild->getAttributes()); - - foreach($tableChild->getChildren() as $tableRow) { - // Check the ID: - $id = explode('-', $tableRow->getAttribute('id')); - - if(in_array($id[1], $ids)) { - $newTableBody->appendChild($tableRow); - } - } - - $newTable->appendChild($newTableBody); - } - else { - $newTable->appendChild($tableChild); - } - } - - $newForm->appendChild($newTable); - } - elseif($formChild->getName() == 'div' && $formChild->getAttribute('class') == 'actions') { - // Only proceed if you can either edit or delete. Otherwise it would have much sense to have an apply-button here... - if($rules['delete'] == 1 || $rules['edit'] == 1) { - $child = self::findChildren($formChild,'select'); - $child = $child[0]; - - $newSelect = new XMLElement('select', null, $child->getAttributes()); - - foreach($child->getChildren() as $selectChild) { - // See if delete is allowed: - if($selectChild->getAttribute('value') == 'delete' && $rules['delete'] == 1) { - $newSelect->appendChild($selectChild); - } - elseif($selectChild->getName() == 'optgroup' && $rules['edit'] == 1) { - // Check if the field that is edited is not a hidden field, because then editing is not allowed: - $optGroupChildren = $selectChild->getChildren(); - - if(!empty($optGroupChildren)) { - $value = $optGroupChildren[0]->getAttribute('value'); - - $a = explode('-', str_replace('toggle-', '', $value)); - - if(!in_array($a[0], $hiddenFields)) { - $newSelect->appendChild($selectChild); - } - } - } - elseif($selectChild->getName() == 'option' && $selectChild->getAttribute('value') != 'delete' && ($rules['edit'] == 1 || $rules['delete'] == 1)) { - $newSelect->appendChild($selectChild); - } - } - - // if the new select has only one entry, - // it is the dummy entry and we can discard it - if($newSelect->getNumberOfChildren() > 1 ) { - self::replaceChild($formChild, $newSelect); - $newForm->appendChild($formChild); - } - } - } - else { - $newForm->appendChild($formChild); - } - } - - $newContents->appendChild($newForm); - $context['oPage']->Form = $newForm; - } - else { - $newContents->appendChild($contentsChild); - } - } - - $context['oPage']->Contents = $newContents; - } - } - } - } - - /** - * See if there should be made some adjustments to the current page: - * @param $context - * The context - */ - public function makePreAdjustements($context) { - $data = $this->getCurrentAuthorRoleData(); - - if($data == false || Symphony::Author()->isDeveloper()) { - return; - } - - // Check if something needs to be done before anything is done: - $callback = Symphony::Engine()->getPageCallback(); - - // If the user is only allowed to see his or her own entries, then the configuration should be overwritten to a maximum. - if($callback['driver'] == 'publish' && $callback['context']['page'] == 'index') { - $section = $callback['context']['section_handle']; - - foreach($data['sections'] as $id_section => $rules) { - if($rules['handle'] == $section && $rules['own_entries'] == 1) { - Symphony::Configuration()->set('pagination_maximum_rows', PHP_INT_MAX, 'symphony'); - } } } } @@ -554,12 +755,6 @@ private function adjustEntryEditor($context, $callback) { } } else { - if($rules['edit'] == 0) { - foreach(self::findChildren($formChild,'input,select,textarea') as $child) { - $child->setAttribute('disabled','disabled'); - } - } - $newForm->appendChild($formChild); } } diff --git a/extension.meta.xml b/extension.meta.xml index afafe41..9161aab 100644 --- a/extension.meta.xml +++ b/extension.meta.xml @@ -11,6 +11,11 @@ Twisted Interactive http://www.twisted.nl + + Maze Digital + http://maze.digital + jon@maze.digital + @@ -24,5 +29,12 @@ Updated compatibility for Symphony 2.5 + + Re Written Core Access Functionality + - Table view now uses filters to narrow down user permissions + - Entry edit now checks that the user has view/edit permissions before loading the page + - User is given an entry/section not found if they are not allowed to view/edit the said entry/section + - Similar Errors are thrown when trying to create, edit and delete entries without the necessary permissions + diff --git a/readme.md b/readme.md index 71656c8..a838227 100644 --- a/readme.md +++ b/readme.md @@ -3,10 +3,13 @@ Author: Twisted Interactive Website: http://www.twisted.nl +Mantained by: Maze Digital +Website: http://maze.digital + ## What does this extension do? ## In short: this extension allows you to set permissions for each author with roles. -It is the successor of the [Author Section extension](https://github.com/kanduvisla/author_section). +It is the successor of the Author Section extension More detailed: The extension allows you to set the following permissions: @@ -43,8 +46,12 @@ you're safe now, otherwise you have to go into the database, look for the `tbl_e ID to be the highest number in the table. Don't change the other extensions! After that, edit the `tbl_extensions_delegates`-table and change the rows which have `extension_id` set to the old ID, and update them to the new ID. Now everything should work just fine! +### Symphony 3.0 + +The above hack would no longer be necessary once Symphony 3.0 is released as a [custom order](https://github.com/symphonycms/symphony-2/commit/e6dd9d4611a5a153b1d657e3418b5d34ef381d43) is allowed when setting delegates. If you feel brave you can merge this commit with your install, and make sure that the `tbl_extensions_delegates` table has the new order field. You will still have to 'set' the order value manually in the database but no need to hack the core. + ## Sidenotes ## - Fields that are set to 'hidden' will be made hidden with JavaScript. This is to ensure that all the data gets send and stored on a POST-action when an entry is saved. However, this could cause some issues with third-party field-types, although these aren't encountered yet. - Fields that are required cannot be set to 'hidden', since that would cause an error when trying to store the entry. -- If you choose to filter entries (by letting an author only show his/her own entries or using the filter), pagination on the publish pages is disabled. This has to be done, because Symphony doesn't have a hook to alter the query used to retreive the entries. +- Relationships using SBL/Association Field etc are not 'filtered' in this version. So a User can view all entries when linking. Editing entries would still work as per the settings provided to the Author roles. Do not use relationships if you don't want users to see certain related entries. From f82509f018d0a15103b7506177f27c54346a12be Mon Sep 17 00:00:00 2001 From: Jon Mifsud Date: Tue, 16 Feb 2016 11:43:47 +0100 Subject: [PATCH 4/8] add preferences --- extension.driver.php | 27 +++++++++++++++++++++++++-- extension.meta.xml | 6 +++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/extension.driver.php b/extension.driver.php index 88a29bb..689d987 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -66,6 +66,11 @@ public function getSubscribedDelegates() 'delegate' => 'EntryPreDelete', 'callback' => 'entryPreDelete' ), + array( + 'page' => '/system/preferences/', + 'delegate' => 'AddCustomPreferenceFieldsets', + 'callback' => 'appendPreferences' + ), ); } @@ -83,7 +88,7 @@ private function isOwnEntry($entry,$rules){ $canAccess = true; - $fieldId = FieldManager::fetchFieldIDFromElementName('authors',$sectionId); + $fieldId = FieldManager::fetchFieldIDFromElementName((string)Symphony::Configuration()->get('author_field', 'author_roles'),$sectionId); // $field = FieldManager::fetch($fieldId); $fieldType = FieldManager::fetchFieldTypeFromID($fieldId); @@ -367,7 +372,7 @@ public function adjustPublishFiltering($context) { //if section has field Author -- defined by config // $sectionObject = SectionManager::fetch($id_section); - $fieldId = FieldManager::fetchFieldIDFromElementName('authors',$id_section); + $fieldId = FieldManager::fetchFieldIDFromElementName((string)Symphony::Configuration()->get('author_field', 'author_roles'),$id_section); $field = FieldManager::fetch($fieldId); $fieldType = FieldManager::fetchFieldTypeFromID($fieldId); @@ -1033,6 +1038,24 @@ public function saveData($values) { } } + public function appendPreferences($context){ + $group = new XMLElement('fieldset',null,array('class'=>'settings')); + $group->appendChild(new XMLElement('legend', 'Author Roles')); + + + $div = new XMLElement('div',null,array('class'=>'group')); + $label = Widget::Label(); + $input = Widget::Input("settings[author_roles][author_field]", (string)Symphony::Configuration()->get('author_field', 'author_roles'), 'text'); + $label->setValue(__('Author Field Handle') . $input->generate()); + $div->appendChild($label); + $div->appendChild(new XMLElement('p','Provide an `author` field handle which is used to detect the author instead of creator of the Symphony entry.',array('class'=>'help'))); + + $group->appendChild($div); + + // Append preferences + $context['wrapper']->appendChild($group); + } + /** * Installation */ diff --git a/extension.meta.xml b/extension.meta.xml index 9161aab..32321ee 100644 --- a/extension.meta.xml +++ b/extension.meta.xml @@ -14,7 +14,7 @@ Maze Digital http://maze.digital - jon@maze.digital + jon@maze.digital @@ -35,6 +35,10 @@ - Entry edit now checks that the user has view/edit permissions before loading the page - User is given an entry/section not found if they are not allowed to view/edit the said entry/section - Similar Errors are thrown when trying to create, edit and delete entries without the necessary permissions + - Added ability to use custom field to determine Author + - Author field uses the author id + - All other fields match the author name with the value + - Author field name has to be consistent across all sections From a88d58c3b45e842c908aa0b3d7fde8e2be313b1e Mon Sep 17 00:00:00 2001 From: Jon Mifsud Date: Fri, 19 Feb 2016 11:12:43 +0100 Subject: [PATCH 5/8] split view/edit own and allow managers to view/set permissions --- content/content.roles.php | 13 +++++-- extension.driver.php | 71 +++++++++++++++++++++++++++++---------- extension.meta.xml | 4 ++- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/content/content.roles.php b/content/content.roles.php index 08eae0b..03b9050 100644 --- a/content/content.roles.php +++ b/content/content.roles.php @@ -170,9 +170,16 @@ public function viewForm() $divEntries = new XMLElement('div', null, array('class'=>'sub entries')); $label = Widget::Label(); // check if this checkbox is checked: - $attributes = $this->checkSection($id, 'own_entries') == 1 ? array('checked'=>'checked') : null; - $input = Widget::Input('section['.$id.'][own_entries]', null, 'checkbox', $attributes); - $label->setValue($input->generate() . ' ' . __('Author can view/edit own entries only')); + $attributes = $this->checkSection($id, 'view_own_entries') == 1 ? array('checked'=>'checked') : null; + $input = Widget::Input('section['.$id.'][view_own_entries]', null, 'checkbox', $attributes); + $label->setValue($input->generate() . ' ' . __('Author can view own entries only')); + $divEntries->appendChild($label); + + $label = Widget::Label(); + // check if this checkbox is checked: + $attributes = $this->checkSection($id, 'edit_own_entries') == 1 ? array('checked'=>'checked') : null; + $input = Widget::Input('section['.$id.'][edit_own_entries]', null, 'checkbox', $attributes); + $label->setValue($input->generate() . ' ' . __('Author can edit own entries only')); $divEntries->appendChild($label); $label = Widget::Label(); diff --git a/extension.driver.php b/extension.driver.php index 689d987..a177538 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -74,6 +74,16 @@ public function getSubscribedDelegates() ); } + /** + * Check if the current user is the Author of the entry + * @param $entry - the entry to be checked + * @param $rules - the rules applying to this section + * @return boolean + */ + private function superseedsPermissions(){ + return Symphony::Author()->isDeveloper() || (Symphony::Author()->isManager() && Symphony::Configuration()->get('manager', 'author_roles') === 'yes'); + } + /** * Check if the current user is the Author of the entry * @param $entry - the entry to be checked @@ -187,7 +197,7 @@ private function filterCanAccess($entryId,$rules){ public function entryPreDelete($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -221,7 +231,7 @@ public function entryPreCreate($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -256,7 +266,7 @@ public function entryPreEdit($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -271,7 +281,7 @@ public function entryPreEdit($context) { $canAccess = false; } - if($rules['own_entries'] == 1 && $canAccess) { + if($rules['edit_own_entries'] == 1 && $canAccess) { $canAccess = $this->isOwnEntry(); } @@ -300,7 +310,7 @@ public function entryPreRender($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -315,7 +325,7 @@ public function entryPreRender($context) { $canAccess = false; } - if($rules['own_entries'] == 1 && $canAccess) { + if($rules['view_own_entries'] == 1 && $canAccess) { $canAccess = $this->isOwnEntry(); } @@ -349,7 +359,7 @@ public function adjustPublishFiltering($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -366,7 +376,7 @@ public function adjustPublishFiltering($context) { ); } - if($rules['own_entries'] == 1) { + if($rules['view_own_entries'] == 1) { // Only get the ID's of the current author to begin with: //if section has field Author -- defined by config @@ -464,7 +474,7 @@ public function fetchNavigation() { public function extendNavigation($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -538,7 +548,7 @@ public function deleteAuthorRole($context) { * The context, providing the form and the author object */ public function addRolePicker($context) { - if(Symphony::Author()->isDeveloper()) { + if($this->superseedsPermissions()) { $group = new XMLElement('fieldset'); $group->setAttribute('class', 'settings'); $group->appendChild(new XMLElement('legend', __('Author Role'))); @@ -619,7 +629,7 @@ private function adjustIndex($context, $callback) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -704,7 +714,7 @@ private static function replaceChild($parent, $child) { private function adjustEntryEditor($context, $callback) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -806,7 +816,7 @@ private function getCurrentAuthorRoleData() { public function modifyAreas($context) { $data = $this->getCurrentAuthorRoleData(); - if($data == false || Symphony::Author()->isDeveloper()) { + if($data == false || $this->superseedsPermissions()) { return; } @@ -827,7 +837,7 @@ public function modifyAreas($context) { * The context */ public function saveAuthorRole($context) { - if(Symphony::Author()->isDeveloper()) { + if($this->superseedsPermissions()) { $id_role = intval($_POST['fields']['role']); $id_author = $context['author']->get('id'); @@ -920,7 +930,8 @@ public function getData($id_role) { A.`create`, A.`edit`, A.`delete`, - A.`own_entries`, + A.`view_own_entries`, + A.`edit_own_entries`, A.`use_filter`, A.`filter_rule` FROM @@ -1011,7 +1022,8 @@ public function saveData($values) { $insert['create'] = isset($info['create']) ? 1 : 0; $insert['edit'] = isset($info['edit']) ? 1 : 0; $insert['delete'] = isset($info['delete']) ? 1 : 0; - $insert['own_entries'] = isset($info['own_entries']) ? 1 : 0; + $insert['view_own_entries'] = isset($info['view_own_entries']) ? 1 : 0; + $insert['edit_own_entries'] = isset($info['edit_own_entries']) ? 1 : 0; $insert['use_filter'] = isset($info['use_filter']) ? 1 : 0; // Filter rule: @@ -1052,6 +1064,18 @@ public function appendPreferences($context){ $group->appendChild($div); + + // Append settings + $label = Widget::Label(); + $input = Widget::Input('settings[author_roles][manager]', 'yes', 'checkbox'); + + if (Symphony::Configuration()->get('manager', 'author_roles') === 'yes') { + $input->setAttribute('checked', 'checked'); + } + + $label->setValue($input->generate() . ' ' . __('Managers should not have restricted access and can set permissions')); + $group->appendChild($label); + // Append preferences $context['wrapper']->appendChild($group); } @@ -1060,6 +1084,8 @@ public function appendPreferences($context){ * Installation */ public function install() { + Symphony::Configuration()->set('manager', 'yes', 'author_roles'); + // Roles table: Symphony::Database()->query(" CREATE TABLE IF NOT EXISTS `tbl_author_roles` ( @@ -1092,7 +1118,8 @@ public function install() { `create` TINYINT(1) unsigned NOT NULL, `edit` TINYINT(1) unsigned NOT NULL, `delete` TINYINT(1) unsigned NOT NULL, - `own_entries` TINYINT(1) unsigned NOT NULL, + `view_own_entries` TINYINT(1) unsigned NOT NULL, + `edit_own_entries` TINYINT(1) unsigned NOT NULL, `use_filter` TINYINT(1) unsigned NOT NULL, `filter_rule` TEXT NOT NULL, PRIMARY KEY (`id`), @@ -1137,6 +1164,16 @@ public function update($previousVersion) { // Update from pre-1.1 to 1.2: return Symphony::Database()->query('ALTER TABLE `tbl_author_roles` ADD `custom_elements` TEXT NULL;'); } + if(version_compare($previousVersion, '2.0', '<')) { + // Update from pre-2.0 to 2.0 + Symphony::Configuration()->set('manager', 'yes', 'author_roles'); + + return Symphony::Database()->query(" + ALTER TABLE `tbl_author_roles_sections` ADD `edit_own_entries` TINYINT(1) unsigned NOT NULL; + UPDATE `tbl_author_roles_sections` SET `edit_own_entries` = `own_entries`; + ALTER TABLE `tbl_author_roles_sections` CHANGE `own_entries` `view_own_entries` TINYINT(1) unsigned NOT NULL; + "); + } } } diff --git a/extension.meta.xml b/extension.meta.xml index 32321ee..19ec2b9 100644 --- a/extension.meta.xml +++ b/extension.meta.xml @@ -29,7 +29,7 @@ Updated compatibility for Symphony 2.5 - + Re Written Core Access Functionality - Table view now uses filters to narrow down user permissions - Entry edit now checks that the user has view/edit permissions before loading the page @@ -39,6 +39,8 @@ - Author field uses the author id - All other fields match the author name with the value - Author field name has to be consistent across all sections + - Differenciated in between Viewing and Editing own entries, one can now view all but only edit own entries + - Added ability to allow Managers to set and have full permissions From 3e80dc9cbd0f46cde6acf471afd87a9845d69006 Mon Sep 17 00:00:00 2001 From: Jon Mifsud Date: Fri, 19 Feb 2016 11:44:24 +0100 Subject: [PATCH 6/8] fix function --- extension.driver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension.driver.php b/extension.driver.php index a177538..fe363d0 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -282,7 +282,7 @@ public function entryPreEdit($context) { } if($rules['edit_own_entries'] == 1 && $canAccess) { - $canAccess = $this->isOwnEntry(); + $canAccess = $this->isOwnEntry($context('entry'),$rules); } // Add or remove ID's from the filter: @@ -326,7 +326,7 @@ public function entryPreRender($context) { } if($rules['view_own_entries'] == 1 && $canAccess) { - $canAccess = $this->isOwnEntry(); + $canAccess = $this->isOwnEntry($context('entry'),$rules); } // Add or remove ID's from the filter: From 23bdc35333d095e621fed65cedc472e0e9df033a Mon Sep 17 00:00:00 2001 From: Jon Mifsud Date: Fri, 19 Feb 2016 11:47:05 +0100 Subject: [PATCH 7/8] fix context call --- extension.driver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension.driver.php b/extension.driver.php index fe363d0..2e2795e 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -282,7 +282,7 @@ public function entryPreEdit($context) { } if($rules['edit_own_entries'] == 1 && $canAccess) { - $canAccess = $this->isOwnEntry($context('entry'),$rules); + $canAccess = $this->isOwnEntry($context['entry'],$rules); } // Add or remove ID's from the filter: @@ -326,7 +326,7 @@ public function entryPreRender($context) { } if($rules['view_own_entries'] == 1 && $canAccess) { - $canAccess = $this->isOwnEntry($context('entry'),$rules); + $canAccess = $this->isOwnEntry($context['entry'],$rules); } // Add or remove ID's from the filter: From 89e25455c7539dcfe11c61ddbce98ce8d3e871f9 Mon Sep 17 00:00:00 2001 From: Jonathan Mifsud Date: Sat, 9 Nov 2019 09:00:03 +0100 Subject: [PATCH 8/8] php 7 update --- extension.driver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension.driver.php b/extension.driver.php index 2e2795e..ec2a1b4 100644 --- a/extension.driver.php +++ b/extension.driver.php @@ -1159,7 +1159,7 @@ public function uninstall() { * The version that is currently installed in this Symphony installation * @return boolean */ - public function update($previousVersion) { + public function update($previousVersion = false) { if(version_compare($previousVersion, '1.2', '<')) { // Update from pre-1.1 to 1.2: return Symphony::Database()->query('ALTER TABLE `tbl_author_roles` ADD `custom_elements` TEXT NULL;');