diff --git a/module/group/control.php b/module/group/control.php index bff9cece32..1745cb98bd 100644 --- a/module/group/control.php +++ b/module/group/control.php @@ -1055,4 +1055,17 @@ class group extends control $privList = $this->group->getRelatedPrivs($privIdList, '', $excludeIdList, $recommedSelect); return print(json_encode($privList)); } + + /** + * AJAX: Get recommend's priv tree. + * + * @param string $data + * @access public + * @return void + */ + public function ajaxGetRecommendTree($data) + { + $this->view->data = json_decode($data); + $this->display(); + } } diff --git a/module/group/css/managepriv.ui.css b/module/group/css/managepriv.ui.css index 40c012f579..89558b5b27 100644 --- a/module/group/css/managepriv.ui.css +++ b/module/group/css/managepriv.ui.css @@ -14,6 +14,7 @@ #mainContainer .main .btn-group .switchBtn {padding: 6px 10px;} #mainContainer .main .btn-group .switchBtn:first-child {border-radius: 4px 0px 0px 4px;} #mainContainer .main .btn-group .switchBtn:last-child {border-radius: 0px 4px 4px 0px;} +#mainContainer .table {table-layout: fixed;} .side .priv-panel {padding: 16px;} .side .priv-panel {background: #fff; padding: 10px 16px; box-shadow: 0 1px 1px rgb(0 0 0 / 5%), 0 2px 6px 0 rgb(0 0 0 / 5%);} .side .priv-panel {height: 40%;} @@ -39,12 +40,16 @@ #privPackageList .package .check-all {display: inline-block; height: 20px;} #privPackageList .package input {margin-top: 0px; margin-left: 0px; left: 0px; height: 20px;} #privPackageList .package label {display: inline-block;} -#privPackageList .privs, #privPackageList .menus-privs {padding: 20px 20px 0 20px; margin: 10px 0px;border: 1px solid #D9DBE1; background-color: #F9FAFB; position: relative; display: block; max-width: unset; box-shadow: unset; z-index: 1;} +#privPackageList .privs, #privPackageList .menus-privs {padding: 20px 20px 0 20px; margin: 10px 0px;border: 1px solid #D9DBE1; background-color: #F9FAFB; position: relative; display: block; max-width: unset; box-shadow: unset; z-index: 1; width: 100%;} +#privPackageList .privs .popover-content {padding: 0;} #privPackageList .menus-privs {margin-top: 35px; border-style: dashed;} #privPackageList .priv-toggle.icon {width: 12px; height: 12px; position: relative; display: inline-block; color: #9EA3B0; margin-left: 4px; line-height: 12px; cursor: pointer; top: 2px;} #privPackageList .priv-toggle.icon:before {content: "\e925"; font-size: 12px; min-width: unset; transform: scale(0.5); font-weight: 900;} -#privPackageList .priv-toggle.icon:after {position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; display: inline-block; content: ""; border: 1px solid; border-radius: 10%;} +#privPackageList .priv-toggle.icon:before {position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; display: inline-block; content: "";} #privPackageList .priv-toggle.icon.open:before {content: "\e926";} +#privPackageList .popover .popover-content {display: inline-block; padding: 0;} +#privPackageList .popover .arrow {border-bottom-color: #D9DBE1; top: -10px;} +#privPackageList .popover .arrow:before {border-bottom-color: #F9FAFB; top: 2px; width: 16px; height: 16px; border: 1px solid #FFF; border-left-color: #D9DBE1; border-top-color: #D9DBE1;} #privPackageList .group-item .checkbox-inline label {padding-left: 0} #privPackageList .group-item {display: inline-block; width: 158px; margin-bottom: 15px;} #privPackageList .group-item > .checkbox-primary {display: inline-block;} @@ -52,3 +57,6 @@ #privPackageList .priv-toggle.icon:before {content: "\e925"; font-size: 12px; min-width: unset; transform: scale(0.5); font-weight: 900;} #privPackageList .priv-toggle.icon:after {position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; display: inline-block; content: ""; border: 1px solid; border-radius: 10%;} #privPackageList .priv-toggle.icon.open:before {content: "\e926";} + +.check-all label.checkbox-indeterminate-block:before {--tw-border-opacity: 1; --tw-bg-opacity: 1; background-color: rgba(var(--color-primary-500-rgb),var(--tw-bg-opacity)); border-color: rgba(var(--color-primary-500-rgb),var(--tw-border-opacity));} +.check-all label.checkbox-indeterminate-block:after {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiB2aWV3Qm94PSIwIDAgMjA0OCAyMDQ4Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTUzNiA1MTJ2MTAyNEg1MTJWNTEyaDEwMjR6Ii8+PC9zdmc+); background-size: cover; opacity: 1;} diff --git a/module/group/js/managepriv.ui.js b/module/group/js/managepriv.ui.js index 2630e96eff..a9a0f527a6 100644 --- a/module/group/js/managepriv.ui.js +++ b/module/group/js/managepriv.ui.js @@ -4,7 +4,111 @@ $(function() $(document).on('change', '#privList > tbody > tr > th .check-all', checkAllChange); $(document).on('change', '#privPackageList > tbody > tr .check-all', checkAllChange); -}) + $(document).on('change', '.priv-footer .check-all', checkAllChange); + $('#privList').on('change', 'tbody > tr .group-item input[type=checkbox]', groupItemChange); + $('#privPackageList .package-column').on('click', '.privs.popover input[type=checkbox]', groupItemChange); + + selectedPrivIdList = Object.values(selectedPrivIdList); + recommedSelect = new Array(); +}); + +$('.side').on('click', '.recommend input[type=checkbox]', (function() +{ + var checked = $(this).prop('checked'); + if($(this).attr('data-has-children') == 'true') + { + $(this).closest('li').find('ul > li input[type=checkbox]').each(function(){ + recommendChange($(this), checked); + }); + } + else + { + recommendChange($(this), checked); + } +// updatePrivTree(selectedPrivIdList); +})); + +$('#privPackageList').on('click', '.package > .priv-toggle.icon', function() +{ + var opened = $(this).hasClass('open'); + + $('#privPackageList .package > .priv-toggle.icon').removeClass('open'); + $('.privs.popover').remove(); + + if(!opened) + { + $(this).addClass('open'); + var moduleName = $(this).closest('.package').attr('data-module'); + var packageID = $(this).closest('.package').attr('data-package'); + + /* The privs should be inserted after which permission package. */ + var perRowPackages = Math.floor($(this).closest('td').width() / $(this).closest('td').find('.package').width()); + var packageIndex = $(this).closest('.package').index() / 2 ; + var appendIndex = (Math.floor(packageIndex / perRowPackages) + 1) * perRowPackages - 1; + + var $privs = $(".privs.hidden[data-module='" + moduleName + "'][data-package='" + packageID + "']") + var $showPrivs = $('
' + + $privs.html().replace(/actions/g, 'showPrivs') + + '
'); + + /* Calculate the triangle position of privs popover. */ + var position = $(this).closest('td').find('.package').width() * (packageIndex % perRowPackages) + 15; + + $showPrivs.find('.arrow').css('left', position + 'px'); + + if($(this).closest('td').find('.package').eq(appendIndex).length == 0) + { + $(this).closest('td').find('.package').eq(-1).after($showPrivs); + $showPrivs.css('margin-bottom', '0'); + } + else + { + $(this).closest('td').find('.package').eq(appendIndex).after($showPrivs); + } + } +}); + +$('#privPackageList .package-column').on('click', '.menus-privs input[type=checkbox]', (function() +{ + var $menusPrivs = $(this).closest('.popover-content').find('.menus-item'); + var $parentPrivs = $(this).closest('.privs').find('.checkbox-primary.check-all'); + var allPrivs = $menusPrivs.length; + var selectPrivs = $menusPrivs.find('input[type=checkbox]:checked').length; + + if(allPrivs > 0 && allPrivs == selectPrivs) + { + $parentPrivs.find('input').attr('checked', true); + $parentPrivs.find('label').removeClass('checkbox-indeterminate-block'); + } + else if(selectPrivs == 0) + { + $parentPrivs.find('input').removeAttr('checked'); + $parentPrivs.find('label').removeClass('checkbox-indeterminate-block'); + } + else + { + $parentPrivs.find('input').removeAttr('checked'); + $parentPrivs.find('label').addClass('checkbox-indeterminate-block'); + } + groupItemChange(); +})); + +$('#privPackageList .package-column').on('click', '.privs .check-all', (function() +{ + var checked = $(this).find('input[type=checkbox]').prop('checked'); + var $children = $(this).closest('.package-column').find('.menus-privs .menus-item'); + if(checked) + { + $children.find('input').attr('checked', true); + } + else + { + $children.find('input').removeAttr('checked'); + } + $children.find('label').removeClass('checkbox-indeterminate-block'); + $(this).find('label').removeClass('checkbox-indeterminate-block'); + changeParentChecked($(this), $(this).closest('.group-item').attr('data-module'), $(this).closest('.group-item').attr('data-package')); +})); /** * When check all change. @@ -29,7 +133,7 @@ function checkAllChange() { var moduleName = $(this).closest('th').attr('data-module'); var packageID = $(this).closest('th').hasClass('package') ? $(this).closest('th').attr('data-package') : ''; - var $children = $(this).closest('th').hasClass('package') ? $(this).closest('tbody').find('[data-divID=' + moduleName + packageID +']') : $(this).closest('tbody').find('[data-module=' + moduleName +']'); + var $children = $(this).closest('th').hasClass('package') ? $(this).closest('tbody').find('[data-divid=' + moduleName + packageID +']') : $(this).closest('tbody').find('[data-module=' + moduleName +']'); $children.find('input[type=checkbox]').prop('checked', checked); $children.find('.checkbox-indeterminate-block').removeClass('checkbox-indeterminate-block'); @@ -37,13 +141,13 @@ function checkAllChange() if(checked) $children.find('input[type=checkbox]').attr('checked', checked); if(!checked) $children.find('input[type=checkbox]').removeAttr('checked'); - // changeParentChecked($(this), moduleName, packageID); + changeParentChecked($(this), moduleName, packageID); } else if($(this).closest('#privPackageList').length > 0) { var moduleName = $(this).parent().attr('data-module'); var packageID = $(this).parent().hasClass('package') ? $(this).parent().attr('data-package') : ''; - var $children = $(this).parent().hasClass('package') ? $(this).closest('td').find('[data-divID=' + moduleName + packageID + ']') : $(this).closest('tbody').find('[data-module=' + moduleName +']'); + var $children = $(this).parent().hasClass('package') ? $(this).closest('td').find('[data-divid=' + moduleName + packageID + ']') : $(this).closest('tbody').find('[data-module=' + moduleName +']'); $children.find('input[type=checkbox]').prop('checked', checked); $children.find('.checkbox-indeterminate-block').removeClass('checkbox-indeterminate-block'); @@ -51,9 +155,143 @@ function checkAllChange() if(checked) $children.find('input[type=checkbox]').attr('checked', checked); if(!checked) $children.find('input[type=checkbox]').removeAttr('checked'); -// changeParentChecked($(this), moduleName, packageID); + changeParentChecked($(this), moduleName, packageID); } -// updatePrivTree(null); + updatePrivTree(null); +} + +/** + * Change parent item checked. + * + * @access public + * @return void + */ +function changeParentChecked($item, moduleName, packageID) +{ + var moduleAllPrivs = $item.closest('tbody').find('.group-item[data-module=' + moduleName + ']:not(.menus-browse)').length; + var moduleSelectPrivs = $item.closest('tbody').find('.group-item[data-module=' + moduleName + ']:not(.menus-browse)').find('input[type=checkbox]:checked').length; + var $moduleItem = $item.closest('tbody').find('.module[data-module=' + moduleName + ']'); + if($item.closest('tbody').find('.menus.' + moduleName).length > 0) + { + moduleAllPrivs += $item.closest('tbody').find('.menus.' + moduleName + ' input[name^=actions]:not(input[value=browse])').length; + moduleSelectPrivs += $item.closest('tbody').find('.menus.' + moduleName + ' input[name^=actions]:checked:not(input[value=browse])').length; + } + if(moduleSelectPrivs == 0) + { + $moduleItem.find('input[type=checkbox]').prop('checked', false); + $moduleItem.find('label').removeClass('checkbox-indeterminate-block'); + } + else if(moduleAllPrivs == moduleSelectPrivs) + { + $moduleItem.find('input[type=checkbox]').prop('checked', true); + $moduleItem.find('label').removeClass('checkbox-indeterminate-block'); + } + else + { + $moduleItem.find('input[type=checkbox]').prop('checked', false); + $moduleItem.find('label').addClass('checkbox-indeterminate-block'); + } + + if(packageID == '') + { + const allModules = $item.closest('tbody').find('.module input[type=checkbox]').length; + const allCheckedModules = $item.closest('tbody').find('.module input[type=checkbox]:checked').length; + $('#allChecker').prop('checked', allModules == allCheckedModules); + return; + } + + var packageAllPrivs = $item.closest('tbody').find('.group-item[data-divid=' + moduleName + packageID + ']:not(.menus-browse)').length; + var packageSelectPrivs = $item.closest('tbody').find('.group-item[data-divid=' + moduleName + packageID + ']:not(.menus-browse)').find('input[type=checkbox]:checked').length; + var $packageItem = $item.closest('tbody').find('.package[data-divid=' + moduleName + packageID + ']'); + if($item.closest('tbody').find('.menus.' + moduleName).length > 0) + { + packageAllPrivs += $item.closest('tbody').find('.menus.' + moduleName + ' input[name^=actions]:not(input[value=browse])').length; + packageSelectPrivs += $item.closest('tbody').find('.menus.' + moduleName + ' input[name^=actions]:checked:not(input[value=browse])').length; + } + if(packageSelectPrivs == 0) + { + $packageItem.find('input[type=checkbox]').prop('checked', false); + $packageItem.find('label').removeClass('checkbox-indeterminate-block'); + } + else if(packageAllPrivs == packageSelectPrivs) + { + $packageItem.find('input[type=checkbox]').prop('checked', true); + $packageItem.find('label').removeClass('checkbox-indeterminate-block'); + } + else + { + $packageItem.find('input[type=checkbox]').prop('checked', false); + $packageItem.find('label').addClass('checkbox-indeterminate-block'); + } + + const allModules = $item.closest('tbody').find('.module input[type=checkbox]').length; + const allCheckedModules = $item.closest('tbody').find('.module input[type=checkbox]:checked').length; + $('#allChecker').prop('checked', allModules == allCheckedModules); +} + +/** + * update comp tree after click checkbox-label. + * + * @param obj objTree + * @access public + * @return void + */ +function updatePrivTree(privList) +{ + if(privList == null) + { + privList = new Array(); + $('tbody .group-item input:checked').each(function() + { + var privID = $(this).closest('.group-item').attr('data-id'); + if($(this).closest('popover-content').length == 0 && privList.indexOf(privID) < 0) privList.push(privID); + }); + selectedPrivIdList = privList; + } + + $.ajax( + { + url: $.createLink('group', 'ajaxGetRelatedPrivs'), + dataType: 'json', + method: 'post', + data: {"privList" : privList.toString(), "recommedSelect": recommedSelect.toString(), "excludeIdList": Object.values(excludeIdList).toString()}, + success: function(data) + { + if(data.depend == undefined || data.depend.length == 0) + { + $('.side .menuTree.depend').empty(); + $('.side .menuTree.depend').closest('.priv-panel').find('.table-empty-tip').removeClass('hidden'); + } + else + { + $(".menuTree.depend").data('zui.tree').reload(data.depend); + $('.side .menuTree.depend').closest('.priv-panel').find('.table-empty-tip').addClass('hidden'); + } + + if(data.recommend == undefined || data.recommend.length == 0) + { + $('.side .menuTree.recommend').empty(); + $('.side .menuTree.recommend').closest('.priv-panel').find('.table-empty-tip').removeClass('hidden'); + } + else + { + $('.side .menuTree.recommend').closest('.priv-panel').find('.table-empty-tip').addClass('hidden'); + $('.side .menuTree.recommend').load($.createLink('group', 'ajaxGetRecommendTree', 'data=' + JSON.stringify(data.recommend))); + $('.menuTree.recommend > li').each(function(){ + var allItemLength = $(this).find('ul input[type=checkbox]').length; + var checkedItemLength = $(this).find('ul input[type=checkbox]:checked').length; + if(checkedItemLength > 0 && allItemLength == checkedItemLength) + { + $(this).find('.check-all input').attr('checked', true); + } + else if(checkedItemLength > 0) + { + $(this).find('.check-all label').addClass('checkbox-indeterminate-block'); + } + }); + } + } + }); } window.onsize = function() @@ -122,3 +360,113 @@ function setActions() updatePrivList('package', hasSelectedPackage); } + +/** + * Whrn group item change. + * + * @param object $item + * @access public + * @return void + */ +function groupItemChange() +{ + var checked = $(this).prop('checked'); + if(checked) + { + $(this).attr('checked', true); + } + else + { + $(this).removeAttr('checked'); + } + if($('#privPackageList').length > 0) + { + var dataid = $(this).attr('data-id'); + var $priv = $('#privPackageList').find('.group-item input[data-id="' + dataid + '"]'); + if(checked) + { + $priv.attr('checked', true); + } + else + { + $priv.removeAttr('checked'); + } + } + + var moduleName = $(this).closest('.group-item').attr('data-module'); + var packageID = $(this).closest('.group-item').attr('data-package'); + changeParentChecked($(this), moduleName, packageID); + + var privID = $(this).closest('.group-item').attr('data-id'); + if(privID != 0) + { + var index = selectedPrivIdList.indexOf(privID); + + if(privID > 0 && index < 0 && checked) selectedPrivIdList.push(privID); + if(privID > 0 && index > -1 && !checked) selectedPrivIdList.splice(index, 1); + + updatePrivTree(selectedPrivIdList); + } +} + +/** + * When recommend privs change. + * + * @param object $item + * @param bool $checked + * @access public + * @return void + */ +function recommendChange($item, checked) +{ + var privModule = $item.attr('data-module'); + var privMethod = $item.attr('data-method'); + var $actionItem = $('#privList').length > 0 ? $('#privList input[data-id="' + privModule + '-' + privMethod + '"]') : $('#privPackageList input[data-id="' + privModule + '-' + privMethod + '"]'); + $actionItem.prop('checked', checked); + if(checked) + { + $item.attr('checked', true); + $actionItem.attr('checked', true); + } + else + { + $item.removeAttr('checked'); + $actionItem.removeAttr('checked'); + } + var moduleName = $actionItem.closest('tr').find('.package').attr('data-module'); + var packageID = $('#privList').length > 0 ? $actionItem.closest('tr').find('.package').attr('data-package') : $actionItem.closest('.privs').attr('data-package'); + changeParentChecked($actionItem, moduleName, packageID); + + var $parentItem = $item.closest('ul').closest('li').find('input[data-has-children="true"]'); + var allItemLength = $item.closest('ul').find('input[type=checkbox]').length; + var checkedItemLength = $item.closest('ul').find('input[type=checkbox]:checked').length; + if(checkedItemLength == 0) + { + $parentItem.removeAttr('checked'); + $parentItem.closest('.checkbox-primary').find('label').removeClass('checkbox-indeterminate-block'); + } + else if(allItemLength == checkedItemLength) + { + $parentItem.attr('checked', true); + $parentItem.closest('.checkbox-primary').find('label').removeClass('checkbox-indeterminate-block'); + } + else + { + $parentItem.removeAttr('checked'); + $parentItem.closest('.checkbox-primary').find('label').addClass('checkbox-indeterminate-block'); + } + + var privID = $item.attr('data-relationpriv'); + if(privID != 0) + { + var index = selectedPrivIdList.indexOf(privID); + + if(privID > 0 && index < 0 && checked) selectedPrivIdList.push(privID); + if(privID > 0 && index > -1 && !checked) selectedPrivIdList.splice(index, 1); + + index = recommedSelect.indexOf(privID); + + if(privID > 0 && index < 0 && checked) recommedSelect.push(privID); + if(privID > 0 && index > -1 && !checked) recommedSelect.splice(index, 1); + } +} diff --git a/module/group/ui/privbypackage.html.php b/module/group/ui/privbypackage.html.php index 80a915ec9d..33e5bf430c 100644 --- a/module/group/ui/privbypackage.html.php +++ b/module/group/ui/privbypackage.html.php @@ -14,6 +14,10 @@ if($group->role == 'limited') } else { + jsVar('window.selectedPrivIdList', $selectedPrivIdList); + jsVar('excludeIdList', $excludePrivsIdList); + jsVar('groupID', $groupID); + jsVar('type', $type); $params = "type=byPackage¶m=$groupID&menu=%s&version=$version"; $mainNavItems = null; $i = 0; @@ -82,7 +86,7 @@ else set('data-id', "{$moduleName}-{$method}"), set('data-module', $moduleName), set('data-package', 0), - set('data-divID', "{$moduleName}0"), + set('data-divid', "{$moduleName}0"), checkbox ( set::name("actions[{$moduleName}]"), @@ -96,20 +100,21 @@ else return $methodItems; }; - function getPrivsItems($privs, $moduleName) + function getPrivsItems($privs, $moduleName, $packageID, $groupPrivs) { global $lang; $privsBox = array(); foreach($privs as $privID => $priv) { if(!empty($lang->$moduleName->menus) && ($priv->method == 'browse' or in_array($priv->method, array_keys($lang->$moduleName->menus)))) continue; + $privMethod = isset($groupPrivs[$priv->module][$priv->method]) ? $priv->method : ''; $privsBox[] = div ( setClass('group-item'), set('data-id', zget($priv, 'id', 0)), set('data-module', $moduleName), set('data-package', $packageID), - set('data-divID', "{$moduleName}{$packageID}"), + set('data-divid', "{$moduleName}{$packageID}"), div ( setClass('checkbox-primary'), @@ -117,7 +122,8 @@ else ( set::name("actions[{$priv->module}]"), setID("actions[{$priv->module}]{$priv->method}"), - set::value(isset($groupPrivs[$priv->module][$priv->method]) ? $priv->method : ''), + set::value($priv->method), + set::checked($priv->method == $privMethod), set::text($priv->name), set('data-id', $priv->action), ) @@ -151,7 +157,7 @@ else set('data-package', $packageID), set('all-privs', $packagePrivs), set('select-privs', $packageSelect), - set('data-divID', "{$moduleName}{$packageID}"), + set('data-divid', "{$moduleName}{$packageID}"), div ( setClass('checkbox-primary checkbox-inline checkbox-left check-all'), @@ -171,7 +177,7 @@ else setClass('privs hidden'), set('data-module', $moduleName), set('data-package', $packageID), - set('data-divID', "{$moduleName}{$packageID}"), + set('data-divid', "{$moduleName}{$packageID}"), div(setClass('arrow')), div ( @@ -182,7 +188,7 @@ else set('data-id', 0), set('data-module', $moduleName), set('data-package', 0), - set('data-divID', "{$moduleName}{$packageID}"), + set('data-divid', "{$moduleName}0"), div ( setClass('checkbox-primary checkbox-inline checkbox-left check-all'), @@ -200,7 +206,7 @@ else setClass('menus-privs hidden'), set('data-module', $moduleName), set('data-package', $packageID), - set('data-divID', "{$moduleName}{$packageID}"), + set('data-divid', "{$moduleName}{$packageID}"), div(setClass('arrow')), div ( @@ -209,7 +215,7 @@ else ) ) ) : null, - getPrivsItems($privs, $moduleName) + getPrivsItems($privs, $moduleName, $packageID, $groupPrivs) ) ) );