diff --git a/administrator/components/com_admin/script.php b/administrator/components/com_admin/script.php
index 0aaa9ad97ed99..37fd7aa329f5f 100644
--- a/administrator/components/com_admin/script.php
+++ b/administrator/components/com_admin/script.php
@@ -1423,6 +1423,7 @@ public function deleteUnexistingFiles()
'/administrator/manifests/libraries/simplepie.xml',
'/administrator/templates/isis/js/jquery.js',
'/administrator/templates/isis/js/bootstrap.min.js',
+ '/media/system/js/permissions.min.js',
);
// TODO There is an issue while deleting folders using the ftp mode
diff --git a/administrator/components/com_config/controller/application/sendtestmail.php b/administrator/components/com_config/controller/application/sendtestmail.php
index fd70e3ca4b859..56070cc987955 100644
--- a/administrator/components/com_config/controller/application/sendtestmail.php
+++ b/administrator/components/com_config/controller/application/sendtestmail.php
@@ -24,22 +24,27 @@ class ConfigControllerApplicationSendtestmail extends JControllerBase
*/
public function execute()
{
+ // Send json mime type.
+ $this->app->mimeType = 'application/json';
+ $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
+ $this->app->sendHeaders();
+
+ // Check if user token is valid.
if (!JSession::checkToken('get'))
{
- $this->app->enqueueMessage(JText::_('JINVALID_TOKEN'));
- $this->app->redirect('index.php');
+ $this->app->enqueueMessage(JText::_('JINVALID_TOKEN'), 'error');
+ echo new JResponseJson;
+ $this->app->close();
}
+ // Check if the user is authorized to do this.
if (!JFactory::getUser()->authorise('core.admin'))
{
- $this->app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'));
- $this->app->redirect('index.php');
+ $this->app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error');
+ echo new JResponseJson;
+ $this->app->close();
}
- $this->app->mimeType = 'application/json';
- $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
- $this->app->sendHeaders();
-
$model = new ConfigModelApplication;
echo new JResponseJson($model->sendTestMail());
$this->app->close();
diff --git a/administrator/components/com_config/controller/application/store.php b/administrator/components/com_config/controller/application/store.php
index 0beb19aa0e45b..42b653695c6a8 100644
--- a/administrator/components/com_config/controller/application/store.php
+++ b/administrator/components/com_config/controller/application/store.php
@@ -24,36 +24,21 @@ class ConfigControllerApplicationStore extends JControllerBase
*/
public function execute()
{
- // Check if the user is authorized to do this.
- if (!JFactory::getUser()->authorise('core.admin'))
- {
- echo new JResponseJson(json_encode(false), JText::_('JERROR_ALERTNOAUTHOR'));
-
- JFactory::getApplication()->close();
- }
-
- // Get Post DATA
- $permissions = array(
- 'component' => $this->input->get->get('comp'),
- 'action' => $this->input->get->get('action'),
- 'rule' => $this->input->get->get('rule'),
- 'value' => $this->input->get->get('value'),
- 'title' => $this->input->get->get('title', '', 'RAW')
- );
+ // Send json mime type.
+ $this->app->mimeType = 'application/json';
+ $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
+ $this->app->sendHeaders();
- if (!(substr($permissions['component'], -6) === '.false'))
- {
- // Load Permissions from Session and send to Model
- $model = new ConfigModelApplication;
- $response = $model->storePermissions($permissions);
-
- echo new JResponseJson(json_encode($response), $response['message']);
- }
- else
+ // Check if user token is valid.
+ if (!JSession::checkToken('get'))
{
- echo new JResponseJson(json_encode(false), 0);
+ $this->app->enqueueMessage(JText::_('JINVALID_TOKEN'), 'error');
+ echo new JResponseJson;
+ $this->app->close();
}
- JFactory::getApplication()->close();
+ $model = new ConfigModelApplication;
+ echo new JResponseJson($model->storePermissions());
+ $this->app->close();
}
}
diff --git a/administrator/components/com_config/model/application.php b/administrator/components/com_config/model/application.php
index adf76d0f091fd..ffc280cf40976 100644
--- a/administrator/components/com_config/model/application.php
+++ b/administrator/components/com_config/model/application.php
@@ -370,185 +370,328 @@ private function writeConfigFile(Registry $config)
*
* @since 3.5
*/
- public function storePermissions($permission)
+ public function storePermissions($permission = null)
{
- $result = array(
- 'text' => '',
- 'class' => '',
- 'result' => true,
- 'message' => '',
- );
+ $app = JFactory::getApplication();
+ $user = JFactory::getUser();
+
+ if (is_null($permission))
+ {
+ // Get data from input.
+ $permission = array(
+ 'component' => $app->input->get('comp'),
+ 'action' => $app->input->get('action'),
+ 'rule' => $app->input->get('rule'),
+ 'value' => $app->input->get('value'),
+ 'title' => $app->input->get('title', '', 'RAW')
+ );
+ }
+
+ // We are creating a new item so we don't have an item id so don't allow.
+ if (substr($permission['component'], -6) === '.false')
+ {
+ $app->enqueueMessage(JText::_('JLIB_RULES_SAVE_BEFORE_CHANGE_PERMISSIONS'), 'error');
+
+ return false;
+ }
+
+ // Check if the user is authorized to do this.
+ if (!$user->authorise('core.admin', $permission['component']))
+ {
+ $app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error');
+
+ return false;
+ }
+
+ // Check if changed group has Super User permissions.
+ $isSuperUserGroupBefore = JAccess::checkGroup($permission['rule'], 'core.admin');
+
+ // Check if current user belongs to changed group.
+ $currentUserBelongsToGroup = in_array((int) $permission['rule'], $user->groups) ? true : false;
+
+ // Get current user groups tree.
+ $currentUserGroupsTree = JAccess::getGroupsByUser($user->id, true);
+
+ // Check if current user belongs to changed group.
+ $currentUserSuperUser = $user->authorise('core.admin');
+
+ // If user is not Super User cannot change the permissions of a group it belongs to.
+ if (!$currentUserSuperUser && $currentUserBelongsToGroup)
+ {
+ $app->enqueueMessage(JText::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_GROUPS'), 'error');
+
+ return false;
+ }
+
+ // If user is not Super User cannot change the permissions of a group it belongs to.
+ if (!$currentUserSuperUser && in_array((int) $permission['rule'], $currentUserGroupsTree))
+ {
+ $app->enqueueMessage(JText::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_PARENT_GROUPS'), 'error');
+
+ return false;
+ }
+
+ // If user is not Super User cannot change the permissions of a Super User Group.
+ if (!$currentUserSuperUser && $isSuperUserGroupBefore && !$currentUserBelongsToGroup)
+ {
+ $app->enqueueMessage(JText::_('JLIB_USER_ERROR_CANNOT_CHANGE_SUPER_USER'), 'error');
+
+ return false;
+ }
+
+ // If user is not Super User cannot change the Super User permissions in any group it belongs to.
+ if ($isSuperUserGroupBefore && $currentUserBelongsToGroup && $permission['action'] === 'core.admin')
+ {
+ $app->enqueueMessage(JText::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'), 'error');
+
+ return false;
+ }
try
{
- // Check if this group has super user permissions
- $isSuperUser = JAccess::checkGroup($permission['rule'], 'core.admin');
+ // Load the current settings for this component.
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName(array('name', 'rules')))
+ ->from($this->db->quoteName('#__assets'))
+ ->where($this->db->quoteName('name') . ' = ' . $this->db->quote($permission['component']));
+
+ $this->db->setQuery($query);
+
+ // Load the results as a list of stdClass objects (see later for more options on retrieving data).
+ $results = $this->db->loadAssocList();
+ }
+ catch (Exception $e)
+ {
+ $app->enqueueMessage($e->getMessage(), 'error');
- // Make sure the super user is not changing the super user status
- if ($isSuperUser && $permission['action'] === 'core.admin')
+ return false;
+ }
+
+ // No record found, let's create one.
+ if (empty($results))
+ {
+ $data = array();
+ $data[$permission['action']] = array($permission['rule'] => $permission['value']);
+
+ $rules = new JAccessRules($data);
+ $asset = JTable::getInstance('asset');
+ $asset->rules = (string) $rules;
+ $asset->name = (string) $permission['component'];
+ $asset->title = (string) $permission['title'];
+
+ // Get the parent asset id so we have a correct tree.
+ $parentAsset = JTable::getInstance('Asset');
+
+ if (strpos($asset->name, '.') !== false)
{
- $result['message'] = JText::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF');
- $result['result'] = false;
+ $assetParts = explode('.', $asset->name);
+ $parentAsset->loadByName($assetParts[0]);
+ $parentAssetId = $parentAsset->id;
}
-
- if ($result['result'])
+ else
{
- // Load the current settings for this component
- $query = $this->db->getQuery(true)
- ->select($this->db->quoteName(array('name', 'rules')))
- ->from($this->db->quoteName('#__assets'))
- ->where($this->db->quoteName('name') . ' = ' . $this->db->quote($permission['component']));
+ $parentAssetId = $parentAsset->getRootId();
+ }
- $this->db->setQuery($query);
+ $asset->setLocation($parentAssetId, 'last-child');
- // Load the results as a list of stdClass objects (see later for more options on retrieving data).
- $results = $this->db->loadAssocList();
+ if (!$asset->check() || !$asset->store())
+ {
+ $app->enqueueMessage(JText::_('JLIB_UNKNOWN'), 'error');
- // No record found, let's create one
- if (empty($results))
+ return false;
+ }
+ }
+ else
+ {
+ // Decode the rule settings.
+ $temp = json_decode($results[0]['rules'], true);
+
+ // Check if a new value is to be set.
+ if (isset($permission['value']))
+ {
+ // Check if we already have an action entry.
+ if (!isset($temp[$permission['action']]))
{
- $data = array();
- $data[$permission['action']] = array();
- $data[$permission['action']] = array($permission['rule'] => $permission['value']);
-
- $rules = new JAccessRules($data);
- $asset = JTable::getInstance('asset');
- $asset->rules = (string) $rules;
- $asset->name = (string) $permission['component'];
- $asset->title = (string) $permission['title'];
-
- if (!$asset->check() || !$asset->store())
- {
- $result['message'] = JText::_('SOME_ERROR_CODE');
- $result['result'] = false;
- }
+ $temp[$permission['action']] = array();
}
- else
+
+ // Check if we already have a rule entry.
+ if (!isset($temp[$permission['action']][$permission['rule']]))
{
- // Decode the rule settings
- $temp = json_decode($results[0]['rules'], true);
-
- // Check if a new value is to be set
- if (isset($permission['value']))
- {
- // Check if we already have an action entry
- if (!isset($temp[$permission['action']]))
- {
- $temp[$permission['action']] = array();
- }
-
- // Check if we already have a rule entry
- if (!isset($temp[$permission['action']][$permission['rule']]))
- {
- $temp[$permission['action']][$permission['rule']] = array();
- }
-
- // Set the new permission
- $temp[$permission['action']][$permission['rule']] = (int) $permission['value'];
-
- // Check if we have an inherited setting
- if (strlen($permission['value']) === 0)
- {
- unset($temp[$permission['action']][$permission['rule']]);
- }
- }
- else
- {
- // There is no value so remove the action as it's not needed
- unset($temp[$permission['action']]);
- }
-
- // Store the new permissions
- $temp = json_encode($temp);
- $query = $this->db->getQuery(true)
- ->update($this->db->quoteName('#__assets'))
- ->set($this->db->quoteName('rules') . ' = ' . $this->db->quote($temp))
- ->where($this->db->quoteName('name') . ' = ' . $this->db->quote($permission['component']));
-
- $this->db->setQuery($query)->execute();
+ $temp[$permission['action']][$permission['rule']] = array();
}
- if ($result['result'])
+ // Set the new permission.
+ $temp[$permission['action']][$permission['rule']] = (int) $permission['value'];
+
+ // Check if we have an inherited setting.
+ if (strlen($permission['value']) === 0)
{
- // Need to find the asset id by the name of the component.
- $db = JFactory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__assets'))
- ->where($db->quoteName('name') . ' = ' . $db->quote($permission['component']));
- $db->setQuery($query);
- $assetId = (int) $db->loadResult();
-
- // Get the new calculated setting for this action
- $inheritedRule = JAccess::checkGroup($permission['rule'], $permission['action'], $assetId);
-
- // Get the rules for just this asset (non-recursive).
- $assetRules = JAccess::getAssetRules($assetId);
-
- // Get the actual setting for the action for this group.
- $assetRule = $assetRules->allow($permission['action'], $permission['rule']);
-
- // If we have a super user we do not need to check anything, super users have all the access
- if ($isSuperUser)
- {
- $result['class'] = 'label label-success';
- $result['text'] = '' . JText::_('JLIB_RULES_ALLOWED_ADMIN');
- }
-
- // This is where we show the current effective settings considering current group, path and cascade.
- // Check whether this is a component or global. Change the text slightly.
-
- // We are not a super user
- if (!$isSuperUser)
- {
- // We are explicitly allowed
- if ($assetRule === true)
- {
- if ($inheritedRule === false)
- {
- // A parent group has been set to denied, we cannot overrule that
- $result['class'] = 'label label-important';
- $result['text'] = '' . JText::_('JLIB_RULES_NOT_ALLOWED_ADMIN_CONFLICT');
- }
- else
- {
- $result['class'] = 'label label-success';
- $result['text'] = JText::_('JLIB_RULES_ALLOWED');
- }
- }
- // We are explicitly denied
- elseif ($assetRule === false)
- {
- $result['class'] = 'label label-important';
- $result['text'] = JText::_('JLIB_RULES_NOT_ALLOWED');
- }
- // Nothing is explicitly set, check inheritance
- else
- {
- if ($inheritedRule === null)
- {
- $result['class'] = 'label label-important';
- $result['text'] = JText::_('JLIB_RULES_NOT_ALLOWED');
- }
- elseif ($inheritedRule === true)
- {
- $result['class'] = 'label label-success';
- $result['text'] = JText::_('JLIB_RULES_ALLOWED');
- }
- elseif ($inheritedRule === false)
- {
- $result['class'] = 'label';
- $result['text'] = '' . JText::_('JLIB_RULES_NOT_ALLOWED_LOCKED');
- }
- }
- }
+ unset($temp[$permission['action']][$permission['rule']]);
}
+
}
+ else
+ {
+ // There is no value so remove the action as it's not needed.
+ unset($temp[$permission['action']]);
+ }
+
+ // Store the new permissions.
+ try
+ {
+ $query = $this->db->getQuery(true)
+ ->update($this->db->quoteName('#__assets'))
+ ->set($this->db->quoteName('rules') . ' = ' . $this->db->quote(json_encode($temp)))
+ ->where($this->db->quoteName('name') . ' = ' . $this->db->quote($permission['component']));
+
+ $this->db->setQuery($query)->execute();
+ }
+ catch (Exception $e)
+ {
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+ }
+
+ // All checks done.
+ $result = array(
+ 'text' => '',
+ 'class' => '',
+ 'result' => true,
+ );
+
+ // Show the current effective calculated permission considering current group, path and cascade.
+
+ try
+ {
+ // Get the asset id by the name of the component.
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('id'))
+ ->from($this->db->quoteName('#__assets'))
+ ->where($this->db->quoteName('name') . ' = ' . $this->db->quote($permission['component']));
+
+ $this->db->setQuery($query);
+
+ $assetId = (int) $this->db->loadResult();
+
+ // Get the group parent id of the current group.
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('parent_id'))
+ ->from($this->db->quoteName('#__usergroups'))
+ ->where($this->db->quoteName('id') . ' = ' . (int) $permission['rule']);
+
+ $this->db->setQuery($query);
+
+ $parentGroupId = (int) $this->db->loadResult();
+
+ // Count the number of child groups of the current group.
+ $query = $this->db->getQuery(true)
+ ->select('COUNT(' . $this->db->quoteName('id') . ')')
+ ->from($this->db->quoteName('#__usergroups'))
+ ->where($this->db->quoteName('parent_id') . ' = ' . (int) $permission['rule']);
+
+ $this->db->setQuery($query);
+
+ $totalChildGroups = (int) $this->db->loadResult();
}
catch (Exception $e)
{
- $result['message'] = $e->getMessage();
- $result['result'] = false;
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ // Clear access statistics.
+ JAccess::clearStatics();
+
+ // After current group permission is changed we need to check again if the group has Super User permissions.
+ $isSuperUserGroupAfter = JAccess::checkGroup($permission['rule'], 'core.admin');
+
+ // Get the rule for just this asset (non-recursive) and get the actual setting for the action for this group.
+ $assetRule = JAccess::getAssetRules($assetId)->allow($permission['action'], $permission['rule']);
+
+ // Get the group, group parent id, and group global config recursive calculated permission for the chosen action.
+ $inheritedGroupRule = JAccess::checkGroup($permission['rule'], $permission['action'], $assetId);
+ $inheritedGroupGlobalRule = JAccess::checkGroup($permission['rule'], $permission['action']);
+ $inheritedParentGroupRule = JAccess::checkGroup($parentGroupId, $permission['action'], $assetId);
+
+ // Current group is a Super User group, so calculated setting is "Allowed (Super User)".
+ if ($isSuperUserGroupAfter)
+ {
+ $result['class'] = 'label label-success';
+ $result['text'] = '' . JText::_('JLIB_RULES_ALLOWED_ADMIN');
+ }
+ // Not super user.
+ else
+ {
+ // First get the real recursive calculated setting and add (Inherited) to it.
+
+ // If recursive calculated setting is "Denied" or null. Calculated permission is "Not Allowed (Inherited)".
+ if ($inheritedGroupRule === null || $inheritedGroupRule === false)
+ {
+ $result['class'] = 'label label-important';
+ $result['text'] = JText::_('JLIB_RULES_NOT_ALLOWED_INHERITED');
+ }
+ // If recursive calculated setting is "Allowed". Calculated permission is "Allowed (Inherited)".
+ else
+ {
+ $result['class'] = 'label label-success';
+ $result['text'] = JText::_('JLIB_RULES_ALLOWED_INHERITED');
+ }
+
+ // Second part: Overwrite the calculated permissions labels if there is an explicity permission in the current group.
+
+ // If there is an explicity permission "Not Allowed". Calculated permission is "Not Allowed".
+ if ($assetRule === false)
+ {
+ $result['class'] = 'label label-important';
+ $result['text'] = JText::_('JLIB_RULES_NOT_ALLOWED');
+ }
+ // If there is an explicity permission is "Allowed". Calculated permission is "Allowed".
+ elseif ($assetRule === true)
+ {
+ $result['class'] = 'label label-success';
+ $result['text'] = JText::_('JLIB_RULES_ALLOWED');
+ }
+
+ // Third part: Overwrite the calculated permissions labels for special cases.
+
+ // User in in global config Root (Public)?
+ $isGlobalConfig = (empty($permission['component']) || $permission['component'] === 'root.1') ? true : false;
+
+ // Global configuration with "Not Set" permission. Calculated permission is "Not Allowed (Default)".
+ if (empty($parentGroupId) && $isGlobalConfig === true && $assetRule === null)
+ {
+ $result['class'] = 'label label-important';
+ $result['text'] = JText::_('JLIB_RULES_NOT_ALLOWED_DEFAULT');
+ }
+ // Component/item root level with explicit "Denied" permission at Global configuration. Calculated permission is "Not Allowed (Locked)".
+ elseif (empty($parentGroupId) && $isGlobalConfig === false && $inheritedParentGroupRule === null && $inheritedGroupGlobalRule === false)
+ {
+ $result['class'] = 'label label-important';
+ $result['text'] = '' . JText::_('JLIB_RULES_NOT_ALLOWED_LOCKED');
+ }
+ // Some parent group has an explicit "Denied". Calculated permission is "Not Allowed (Locked)".
+ elseif ($inheritedParentGroupRule === false)
+ {
+ $result['class'] = 'label label-important';
+ $result['text'] = '' . JText::_('JLIB_RULES_NOT_ALLOWED_LOCKED');
+ }
+ }
+
+ // If removed or added super user from group, we need to refresh the page to recalculate all settings.
+ if ($isSuperUserGroupBefore != $isSuperUserGroupAfter)
+ {
+ $app->enqueueMessage(JText::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_PERMISSIONS'), 'notice');
+ }
+
+ // If this group has child groups, we need to refresh the page to recalculate the child settings.
+ if ($totalChildGroups > 0)
+ {
+ $app->enqueueMessage(JText::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_CHILDS_PERMISSIONS'), 'notice');
}
return $result;
diff --git a/administrator/components/com_config/view/application/html.php b/administrator/components/com_config/view/application/html.php
index 84588a2516e11..5c16eea8e8091 100644
--- a/administrator/components/com_config/view/application/html.php
+++ b/administrator/components/com_config/view/application/html.php
@@ -73,13 +73,6 @@ public function render()
$this->userIsSuperAdmin = $user->authorise('core.admin');
- // Add strings for translations in Javascript.
- JText::script('COM_CONFIG_SENDMAIL_JS_ERROR_CONNECTION_ABORT');
- JText::script('COM_CONFIG_SENDMAIL_JS_ERROR_NO_CONTENT');
- JText::script('COM_CONFIG_SENDMAIL_JS_ERROR_OTHER');
- JText::script('COM_CONFIG_SENDMAIL_JS_ERROR_PARSE');
- JText::script('COM_CONFIG_SENDMAIL_JS_ERROR_TIMEOUT');
-
$this->addToolbar();
return parent::render();
diff --git a/administrator/components/com_config/view/application/tmpl/default_mail.php b/administrator/components/com_config/view/application/tmpl/default_mail.php
index 002cdc3af5349..4ae388305e860 100644
--- a/administrator/components/com_config/view/application/tmpl/default_mail.php
+++ b/administrator/components/com_config/view/application/tmpl/default_mail.php
@@ -10,14 +10,27 @@
defined('_JEXEC') or die;
JHtml::script('system/sendtestmail.js', false, true);
-JFactory::getDocument()->addScriptDeclaration('
- var sendtestmail_url = "' . addslashes(JUri::base()) . 'index.php?option=com_config&task=config.sendtestmail.application&format=json&' . JSession::getFormToken() . '=1";
- ');
+
+// Load JavaScript message titles
+JText::script('ERROR');
+JText::script('WARNING');
+JText::script('NOTICE');
+JText::script('MESSAGE');
+
+// Add strings for JavaScript error translations.
+JText::script('JLIB_JS_AJAX_ERROR_CONNECTION_ABORT');
+JText::script('JLIB_JS_AJAX_ERROR_NO_CONTENT');
+JText::script('JLIB_JS_AJAX_ERROR_OTHER');
+JText::script('JLIB_JS_AJAX_ERROR_PARSE');
+JText::script('JLIB_JS_AJAX_ERROR_TIMEOUT');
+
+// Ajax request data.
+$ajaxUri = JRoute::_('index.php?option=com_config&task=config.sendtestmail.application&format=json&' . JSession::getFormToken() . '=1');
$this->name = JText::_('COM_CONFIG_MAIL_SETTINGS');
$this->fieldsname = 'mail';
echo JLayoutHelper::render('joomla.content.options_default', $this);
-echo '