[Rt-commit] rt branch, 3.9-trunk, updated. rt-3.8.8-687-gbeda996

Thomas Sibley trs at bestpractical.com
Fri Sep 3 14:48:08 EDT 2010


The branch, 3.9-trunk has been updated
       via  beda996dd9748b1f6ced33578cd72e7057b1b3c5 (commit)
       via  c09723d936967f6929c80f32206d5c93ce312910 (commit)
       via  3628036d29ebead5505f9059af4508a14b425bd5 (commit)
       via  249edb554ec49326ebbfb4b5ad57cf5aac3e23cc (commit)
       via  71c539e174405efd7b19fa5db62dc9e4ae058bbf (commit)
       via  92caed09ad97d68dd590db23055c4d6652e6884e (commit)
       via  f4bb682254078968216d63ee1766f3b2fb65781e (commit)
       via  25f5dc159c508d4e089b96196a2ac4fab461349f (commit)
       via  ce648f7a2f9734c6095c266514dceddfcc7e8bde (commit)
       via  ec2817c0918e961e3ea92c240a88d7fde3b4243f (commit)
       via  23162aa0f996c7d763a5e051f3c1e9c3c9796781 (commit)
       via  c688bf7e5765a7f3f7364b0e5f3daca80a271abf (commit)
       via  b70294586642168f126bede4b3e715f55189e74a (commit)
       via  8e43a69bfd68427766376a3682c902f198e7161b (commit)
       via  d4b552a6c5415ade24c68a0cecffb8f527c3bfdc (commit)
       via  a2f018cba22512c4bc22930446d557f5de2c6d4e (commit)
       via  6d74fdb24706cc53fd5367f9d084d1bfcf77433c (commit)
       via  71928032bf0ac78b9a139b0a163b3af901287e28 (commit)
       via  8e85e99d08dff694ee166b539d6f3dcb29e1033e (commit)
       via  b9634f3074184c00c22301b9ba720877cc5b02e7 (commit)
       via  c25dd8c7ee5b5c142c16998b3c535ce0aac4dada (commit)
       via  91e7c76901e3eec304735ad8aee250b3d47e97e2 (commit)
       via  8433bae2b74f79f11714476d73dc23d48f6ef7f8 (commit)
       via  ec754151ebd8db5862d35062b1a4d27e47cdb326 (commit)
      from  b789a4537dadc578c5a228e39d5c37e6dd2f1358 (commit)

Summary of changes:
 lib/RT/CustomField_Overlay.pm                      |   32 +++
 lib/RT/Dashboard.pm                                |   13 ++
 lib/RT/GroupMember_Overlay.pm                      |    2 +-
 lib/RT/Group_Overlay.pm                            |   41 ++++-
 lib/RT/Interface/Web.pm                            |  158 ++++++++++++++--
 lib/RT/Queue_Overlay.pm                            |   57 ++++++
 lib/RT/System.pm                                   |   65 ++++++-
 share/html/Admin/CustomFields/GroupRights.html     |   46 +----
 share/html/Admin/CustomFields/UserRights.html      |   44 +----
 share/html/Admin/Elements/EditRights               |  203 ++++++++++++++++++++
 share/html/Admin/Elements/SelectRights             |  121 ------------
 share/html/Admin/Global/GroupRights.html           |   72 +------
 share/html/Admin/Global/UserRights.html            |   49 +----
 share/html/Admin/Groups/GroupRights.html           |   53 +-----
 share/html/Admin/Groups/UserRights.html            |   51 +-----
 share/html/Admin/Queues/GroupRights.html           |   85 ++-------
 share/html/Admin/Queues/UserRights.html            |   52 ++----
 .../SelectScrip => Helpers/Autocomplete/Groups}    |   44 +++--
 share/html/Helpers/Autocomplete/Users              |    3 +
 .../css/base/jquery-ui-1.8.4.custom.modified.css   |   18 ++
 share/html/NoAuth/css/base/jquery-ui.css           |    2 +
 share/html/NoAuth/css/base/main.css                |    2 +
 share/html/NoAuth/css/base/misc.css                |    3 -
 share/html/NoAuth/css/base/rights-editor.css       |   73 +++++++
 share/html/NoAuth/css/web2/forms.css               |    1 +
 share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js |   35 ++++
 share/html/NoAuth/js/userautocomplete.js           |   21 ++-
 t/web/rights.t                                     |   12 +-
 28 files changed, 804 insertions(+), 554 deletions(-)
 create mode 100644 share/html/Admin/Elements/EditRights
 delete mode 100755 share/html/Admin/Elements/SelectRights
 copy share/html/{Admin/Elements/SelectScrip => Helpers/Autocomplete/Groups} (78%)
 mode change 100755 => 100644
 create mode 100644 share/html/NoAuth/css/base/jquery-ui.css
 create mode 100644 share/html/NoAuth/css/base/rights-editor.css
 mode change 100755 => 100644 share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js

- Log -----------------------------------------------------------------
commit ec754151ebd8db5862d35062b1a4d27e47cdb326
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Aug 27 10:45:18 2010 -0400

    Add the jQuery UI Tabs widget

diff --git a/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js b/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js
old mode 100755
new mode 100644
index 673cb70..ed8ad71
--- a/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js
+++ b/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js
@@ -77,6 +77,41 @@ this.element.children(b))},nextPage:function(a){if(this.hasScroll())if(!this.act
 this.first())this.activate(a,this.element.children(":last"));else{var b=this.active.offset().top,c=this.element.height();result=this.element.children("li").filter(function(){var d=e(this).offset().top-b+c-e(this).height();return d<10&&d>-10});result.length||(result=this.element.children(":first"));this.activate(a,result)}else this.activate(a,this.element.children(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element.attr("scrollHeight")},select:function(a){this._trigger("selected",
 a,{item:this.active})}})})(jQuery);
 ;/*
+ * jQuery UI Tabs 1.8.4
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ */
+(function(d,p){function u(){return++v}function w(){return++x}var v=0,x=0;d.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading&#8230;</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(true)},_setOption:function(a,e){if(a=="selected")this.options.collapsible&&
+e==this.options.selected||this.select(e);else{this.options[a]=e;this._tabify()}},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^A-Za-z0-9\-_:\.]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var a=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[a].concat(d.makeArray(arguments)))},_ui:function(a,e){return{tab:a,panel:e,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var a=
+d(this);a.html(a.data("label.tabs")).removeData("label.tabs")})},_tabify:function(a){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var b=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d("li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var j=d(f).attr("href"),l=j.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]||
+(q=d("base")[0])&&l===q.href)){j=f.hash;f.href=j}if(h.test(j))b.panels=b.panels.add(b._sanitizeSelector(j));else if(j!=="#"){d.data(f,"href.tabs",j);d.data(f,"load.tabs",j.replace(/#.*$/,""));j=b._tabId(f);f.href="#"+j;f=d("#"+j);if(!f.length){f=d(c.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(b.panels[g-1]||b.list);f.data("destroy.tabs",true)}b.panels=b.panels.add(f)}else c.disabled.push(g)});if(a){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");
+this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(b._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected=
+this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return b.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");
+if(c.selected>=0&&this.anchors.length){this.panels.eq(c.selected).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");b.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[c.selected],b.panels[c.selected]))});this.load(c.selected)}d(window).bind("unload",function(){b.lis.add(b.anchors).unbind(".tabs");b.lis=b.anchors=b.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"));this.element[c.collapsible?"addClass":
+"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);a=0;for(var i;i=this.lis[a];a++)d(i)[d.inArray(a,c.disabled)!=-1&&!d(i).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+g)};this.lis.bind("mouseover.tabs",
+function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal",function(){e(f,o);b._trigger("show",
+null,b._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");b._trigger("show",null,b._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){b.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);b.element.dequeue("tabs")})}:function(g,f){b.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");b.element.dequeue("tabs")};this.anchors.bind(c.event+".tabs",
+function(){var g=this,f=d(g).closest("li"),j=b.panels.filter(":not(.ui-tabs-hide)"),l=d(b._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||b._trigger("select",null,b._ui(this,l[0]))===false){this.blur();return false}c.selected=b.anchors.index(this);b.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected=-1;c.cookie&&b._cookie(c.selected,c.cookie);b.element.queue("tabs",function(){s(g,
+j)}).dequeue("tabs");this.blur();return false}else if(!j.length){c.cookie&&b._cookie(c.selected,c.cookie);b.element.queue("tabs",function(){r(g,l)});b.load(b.anchors.index(this));this.blur();return false}c.cookie&&b._cookie(c.selected,c.cookie);if(l.length){j.length&&b.element.queue("tabs",function(){s(g,j)});b.element.queue("tabs",function(){r(g,l)});b.load(b.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier.";d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",
+function(){return false})},_getIndex:function(a){if(typeof a=="string")a=this.anchors.index(this.anchors.filter("[href$="+a+"]"));return a},destroy:function(){var a=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e=d.data(this,"href.tabs");if(e)this.href=
+e;var b=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){b.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});a.cookie&&this._cookie(null,a.cookie);return this},add:function(a,e,b){if(b===p)b=this.anchors.length;
+var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,a).replace(/#\{label\}/g,e));a=!a.indexOf("#")?a.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var i=d("#"+a);i.length||(i=d(h.panelTemplate).attr("id",a).data("destroy.tabs",true));i.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(b>=this.lis.length){e.appendTo(this.list);i.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[b]);
+i.insertBefore(this.panels[b])}h.disabled=d.map(h.disabled,function(k){return k>=b?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");i.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[b],this.panels[b]));return this},remove:function(a){a=this._getIndex(a);var e=this.options,b=this.lis.eq(a).remove(),c=this.panels.eq(a).remove();
+if(b.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(a+(a+1<this.anchors.length?1:-1));e.disabled=d.map(d.grep(e.disabled,function(h){return h!=a}),function(h){return h>=a?--h:h});this._tabify();this._trigger("remove",null,this._ui(b.find("a")[0],c[0]));return this},enable:function(a){a=this._getIndex(a);var e=this.options;if(d.inArray(a,e.disabled)!=-1){this.lis.eq(a).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(b){return b!=a});this._trigger("enable",null,
+this._ui(this.anchors[a],this.panels[a]));return this}},disable:function(a){a=this._getIndex(a);var e=this.options;if(a!=e.selected){this.lis.eq(a).addClass("ui-state-disabled");e.disabled.push(a);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))}return this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;this.anchors.eq(a).trigger(this.options.event+".tabs");return this},
+load:function(a){a=this._getIndex(a);var e=this,b=this.options,c=this.anchors.eq(a)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(a).addClass("ui-state-processing");if(b.spinner){var i=d("span",c);i.data("label.tabs",i.html()).html(b.spinner)}this.xhr=d.ajax(d.extend({},b.ajaxOptions,{url:h,success:function(k,n){d(e._sanitizeSelector(c.hash)).html(k);e._cleanup();b.cache&&d.data(c,"cache.tabs",
+true);e._trigger("load",null,e._ui(e.anchors[a],e.panels[a]));try{b.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[a],e.panels[a]));try{b.ajaxOptions.error(k,n,a,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this},url:function(a,
+e){this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.4"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(a,e){var b=this,c=this.options,h=b._rotate||(b._rotate=function(i){clearTimeout(b.rotation);b.rotation=setTimeout(function(){var k=c.selected;b.select(++k<b.anchors.length?k:0)},a);i&&i.stopPropagation()});e=b._unrotate||(b._unrotate=!e?function(i){i.clientX&&b.rotate(null)}:
+function(){t=c.selected;h()});if(a){this.element.bind("tabsshow",h);this.anchors.bind(c.event+".tabs",e);h()}else{clearTimeout(b.rotation);this.element.unbind("tabsshow",h);this.anchors.unbind(c.event+".tabs",e);delete this._rotate;delete this._unrotate}return this}})})(jQuery);
+;/*
  * jQuery UI Datepicker 1.8.4
  *
  * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)

commit 8433bae2b74f79f11714476d73dc23d48f6ef7f8
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Aug 27 10:54:47 2010 -0400

    Add the jQuery UI Tabs CSS that I forgot

diff --git a/share/html/NoAuth/css/base/jquery-ui-1.8.4.custom.modified.css b/share/html/NoAuth/css/base/jquery-ui-1.8.4.custom.modified.css
index 25d7b74..973d812 100755
--- a/share/html/NoAuth/css/base/jquery-ui-1.8.4.custom.modified.css
+++ b/share/html/NoAuth/css/base/jquery-ui-1.8.4.custom.modified.css
@@ -346,6 +346,24 @@
 	margin: -1px;
 }
 /*
+ * jQuery UI Tabs @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*
  * jQuery UI Datepicker @VERSION
  *
  * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)

commit 91e7c76901e3eec304735ad8aee250b3d47e97e2
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Aug 27 16:51:14 2010 -0400

    Fix doc reference to column name

diff --git a/lib/RT/GroupMember_Overlay.pm b/lib/RT/GroupMember_Overlay.pm
index 626a59b..50b0c0f 100755
--- a/lib/RT/GroupMember_Overlay.pm
+++ b/lib/RT/GroupMember_Overlay.pm
@@ -359,7 +359,7 @@ sub Delete {
 
 =head2 MemberObj
 
-Returns an RT::Principal object for the Principal specified by $self->PrincipalId
+Returns an RT::Principal object for the Principal specified by $self->MemberId
 
 =cut
 

commit c25dd8c7ee5b5c142c16998b3c535ce0aac4dada
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Aug 27 17:19:07 2010 -0400

    Checkpoint of the new rights editor /Admin/Elements/EditRights
    
    I replaced the old queue rights pages with the new editor, but they
    aren't completely functioning yet.  Fitting it into the other rights
    pages should be simple from here out though.
    
    TODO
      - Rights need to be split into categories
      - ProcessACLChanges needs to be updated to understand the checkbox format
      - User/group object lists need to be limited to only those with rights granted
      - Add ability to add a user/group using autocomplete textbox

diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
new file mode 100644
index 0000000..25d5586
--- /dev/null
+++ b/share/html/Admin/Elements/EditRights
@@ -0,0 +1,95 @@
+<%args>
+$Context
+$Principals
+</%args>
+<%init>
+use Scalar::Util qw(blessed);
+</%init>
+%# Principals is an array of arrays, where the inner arrays are like:
+%#      [ 'Category name' => $CollectionObj => 'DisplayColumn' => 1 ]
+%# The last value is a boolen determining if the value of DisplayColumn
+%# should be loc()-ed before display.
+
+<script type="text/javascript">
+  jQuery(function() {
+      jQuery(".rights-editor").tabs();
+  });
+</script>
+
+<div class="rights-editor clearfix">
+  <ul>
+<%perl>
+for my $category (@$Principals) {
+    my ($name, $collection, $col, $loc) = @$category;
+</%perl>
+<li class="category"><% $name %></li>
+<%perl>
+    while ( my $obj = $collection->Next ) {
+        my $display = ref $col eq 'CODE' ? $col->($obj) : $obj->$col;
+        my $id = "acl-$name-" . $obj->PrincipalId;
+        $id =~ s/[^a-zA-Z0-9\-]/_/g;
+</%perl>
+<li><a href="#<% $id %>"><% $loc ? loc($display) : $display %></a></li>
+<%perl>
+    }
+}
+</%perl>
+  </ul>
+
+<%perl>
+# Find all our available rights
+my %available_rights;
+if ( blessed($Context) and $Context->can('AvailableRights') ) { 
+    %available_rights = %{$Context->AvailableRights};
+}
+else {
+    %available_rights = ( loc('System Error') => loc("No rights found") );
+}
+
+# Find all the current rights
+my %current_rights;
+for my $collection (map { $_->[1] } @$Principals) {
+    while (my $group = $collection->Next) {
+        my $acls = RT::ACL->new($session{'CurrentUser'});
+        $acls->LimitToObject( $Context );
+        $acls->LimitToPrincipal( Id => $group->PrincipalId );
+        $acls->OrderBy( FIELD => 'RightName' ); 
+
+        while ( my $ace = $acls->Next ) {
+            my $right = $ace->RightName;
+            $current_rights{$group->PrincipalId}->{$right} = 1;
+        }
+    }
+}
+
+# Now generate our rights panels for each principal
+for my $category (@$Principals) {
+    my ($name, $collection, $col, $loc) = @$category;
+    while ( my $obj = $collection->Next ) {
+        my $display = ref $col eq 'CODE' ? $col->($obj) : $obj->$col;
+        my $acldesc = join '-', $obj->PrincipalId, ref($Context), $Context->Id;
+        my $id = "acl-$name-" . $obj->PrincipalId;
+        $id =~ s/[^a-zA-Z0-9\-]/_/g;
+</%perl>
+  <div id="<% $id %>">
+    <h3>Rights for <% $loc ? loc($display) : $display %></h3>
+    <ul class="rights-list">
+% for my $right (keys %available_rights) {
+      <li>
+        <input type="checkbox" class="checkbox"
+               name="GrantRight-<% $acldesc %>"
+               id="GrantRight-<% $acldesc %>-<% $right %>"
+               value="<% $right %>"
+               <% $current_rights{$obj->PrincipalId}->{$right} ? 'checked' : '' %> />
+        <label for="GrantRight-<% $acldesc %>-<% $right %>">
+          <% loc($available_rights{$right}) %>
+        </label>
+      </li>
+% }
+    </ul>
+  </div>
+<%perl>
+    }
+}
+</%perl>
+</div>
diff --git a/share/html/Admin/Queues/GroupRights.html b/share/html/Admin/Queues/GroupRights.html
index 3cbb105..2f5824f 100755
--- a/share/html/Admin/Queues/GroupRights.html
+++ b/share/html/Admin/Queues/GroupRights.html
@@ -47,74 +47,25 @@
 %# END BPS TAGGED BLOCK }}}
 <& /Admin/Elements/Header, Title => loc('Modify group rights for queue [_1]', $QueueObj->Name) &>
 <& /Admin/Elements/QueueTabs, id => $id, 
-    QueueObj => $QueueObj,                                                      
+    QueueObj => $QueueObj,
     current_tab => $current_tab, 
     Title => loc('Modify group rights for queue [_1]', $QueueObj->Name) &>
 <& /Elements/ListActions, actions => \@results &>
 
-  <form method="post" action="GroupRights.html">
-    <input type="hidden" class="hidden" name="id" value="<% $QueueObj->id %>" />
-      
-      
-<h1><&|/l&>System groups</&></h1>
-<table>
+<form method="post" action="GroupRights.html">
+  <input type="hidden" class="hidden" name="id" value="<% $QueueObj->id %>" />
+
+%# XXX TODO: this was just after the opening table tag, put it somewhere reasonable    
 % $m->callback( %ARGS, QueueObj => $QueueObj, results => \@results );
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToSystemInternalGroups();
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% loc($Group->Type) %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object => $QueueObj  &>
-	  </td>
-	</tr>
-% }
-</table>
-<h1><&|/l&>Roles</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToRolesForQueue($QueueObj->Id);
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% loc($Group->Type) %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object => $QueueObj  &>
-	  </td>
-	</tr>
-% }
-</table>
-<h1><&|/l&>User defined groups</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToUserDefinedGroups();    
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% $Group->Name %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object => $QueueObj  &>
-	  </td>
-	</tr>
-% }
-</table>
-            
-      <& /Elements/Submit, Label => loc('Modify Group Rights'), Reset => 1 &>
-      
-  </form>
-  
-<%INIT>
- 
-  #Update the acls.
-  my @results =  ProcessACLChanges(\%ARGS);
 
+  <& /Admin/Elements/EditRights, Context => $QueueObj, Principals => \@principals &>
+
+  <& /Elements/Submit, Label => loc('Modify Group Rights'), Reset => 1 &>
+</form>
+
+<%INIT>
+# Update the acls.
+my @results =  ProcessACLChanges(\%ARGS);
 
 if (!defined $id) {
     Abort(loc("No Queue defined"));
@@ -123,12 +74,26 @@ if (!defined $id) {
 my $QueueObj = RT::Queue->new($session{'CurrentUser'});
 $QueueObj->Load($id) || Abort(loc("Couldn't load queue [_1]",$id));
 
-my $Groups;
-my $current_tab;
-$current_tab = 'Admin/Queues/GroupRights.html?id='.$QueueObj->id;
-    
-</%INIT>
+my $current_tab = 'Admin/Queues/GroupRights.html?id='.$QueueObj->id;
+
+# Principal collections
+my $system = RT::Groups->new($session{'CurrentUser'});
+$system->LimitToSystemInternalGroups();
+
+my $roles = RT::Groups->new($session{'CurrentUser'});
+$roles->LimitToRolesForQueue($QueueObj->Id);
 
+my $groups = RT::Groups->new($session{'CurrentUser'});
+$groups->LimitToUserDefinedGroups();
+# XXX TODO: only find those user groups with rights granted
+
+my @principals = (
+    # Category        collection   column    loc?
+    ['System'      => $system   => 'Type' => 1],
+    ['Roles'       => $roles    => 'Type' => 1],
+    ['User Groups' => $groups   => 'Name' => 0],
+);
+</%INIT>
 <%ARGS>
 $id => undef
 </%ARGS>
diff --git a/share/html/Admin/Queues/UserRights.html b/share/html/Admin/Queues/UserRights.html
index ecfac9d..757991f 100755
--- a/share/html/Admin/Queues/UserRights.html
+++ b/share/html/Admin/Queues/UserRights.html
@@ -47,43 +47,26 @@
 %# END BPS TAGGED BLOCK }}}
 <& /Admin/Elements/Header, Title => loc('Modify user rights for queue [_1]', $QueueObj->Name) &>
 <& /Admin/Elements/QueueTabs, id => $id,
-    QueueObj => $QueueObj,                                                      
+    QueueObj => $QueueObj,
     current_tab => $current_tab, 
     Title => loc('Modify user rights for queue [_1]', $QueueObj->Name) &>
 <& /Elements/ListActions, actions => \@results &>
 
-  <form method="post" action="UserRights.html">
-    <input type="hidden" class="hidden" name="id" value="<% $QueueObj->id %>" />
-      
-      
-<table>
+<form method="post" action="UserRights.html">
+  <input type="hidden" class="hidden" name="id" value="<% $QueueObj->id %>" />
+
+%# XXX TODO put this somewhere more reasonable      
 % $m->callback( %ARGS, QueueObj => $QueueObj, results => \@results );
-%	while (my $Member = $Users->Next()) {
-% my $UserObj = $Member->MemberObj->Object();
-% my $group = RT::Group->new($session{'CurrentUser'});
-% $group->LoadACLEquivalenceGroup($Member->MemberObj);
-  <tr align="right"> 
-	<td valign="top"><& /Elements/ShowUser, User => $UserObj &></td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId=> $group->PrincipalId,
-        Object => $QueueObj  &>
-	  </td>
-	</tr>
-% }
-      </table>
-            
-      <& /Elements/Submit, Label => loc('Modify User Rights'), Reset => 1 &>
-      
-  </form>
-  
-<%INIT>
- 
-  #Update the acls.
-  my @results =  ProcessACLChanges(\%ARGS);
 
-# {{{ Deal with setting up the display of current rights.
+  <& /Admin/Elements/EditRights, Context => $QueueObj, Principals => \@principals &>
+ 
+  <& /Elements/Submit, Label => loc('Modify User Rights'), Reset => 1 &>
 
+</form>
 
+<%INIT>
+# Update the acls.
+my @results =  ProcessACLChanges(\%ARGS);
 
 if (!defined $id) {
     Abort(loc("No Queue defined"));
@@ -92,16 +75,17 @@ if (!defined $id) {
 my $QueueObj = RT::Queue->new($session{'CurrentUser'});
 $QueueObj->Load($id) || Abort(loc("Couldn't load queue [_1]",$id));
 
-# Find out which users we want to display ACL selects for
+# Find out which users we want to display ACLs for
 my $Privileged = RT::Group->new($session{'CurrentUser'});
 $Privileged->LoadSystemInternalGroup('Privileged');
-my $Users = $Privileged->MembersObj();
+my $Users = $Privileged->UserMembersObj();
+
+my $display = sub {
+    $m->scomp('/Elements/ShowUser', User => $_[0], NoEscape => 1)
+};
+my @principals = (['Users' => $Users => $display => 0]);
 
-    
-  
-# }}}
-my $current_tab;
-$current_tab = 'Admin/Queues/UserRights.html?id='.$QueueObj->id;
+my $current_tab = 'Admin/Queues/UserRights.html?id='.$QueueObj->id;
 </%INIT>
 
 <%ARGS>
diff --git a/share/html/NoAuth/css/base/jquery-ui.css b/share/html/NoAuth/css/base/jquery-ui.css
new file mode 100644
index 0000000..b59e22a
--- /dev/null
+++ b/share/html/NoAuth/css/base/jquery-ui.css
@@ -0,0 +1,2 @@
+ at import "jquery-ui-1.8.4.custom.modified.css";
+ at import "ui.timepickr.css";
diff --git a/share/html/NoAuth/css/base/main.css b/share/html/NoAuth/css/base/main.css
index 753c39f..76a4b15 100644
--- a/share/html/NoAuth/css/base/main.css
+++ b/share/html/NoAuth/css/base/main.css
@@ -47,8 +47,10 @@
 %# END BPS TAGGED BLOCK }}}
 % $m->callback(CallbackName => 'Begin');
 
+ at import "jquery-ui.css";
 @import "misc.css";
 @import "ticket-form.css";
+ at import "rights-editor.css";
 
 % $m->callback(CallbackName => 'End');
 
diff --git a/share/html/NoAuth/css/base/misc.css b/share/html/NoAuth/css/base/misc.css
index 3281c31..383dc04 100644
--- a/share/html/NoAuth/css/base/misc.css
+++ b/share/html/NoAuth/css/base/misc.css
@@ -45,9 +45,6 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
- at import "jquery-ui-1.8.4.custom.modified.css";
- at import "ui.timepickr.css";
-
 .hide, .hidden { display: none !important; }
 
 .clear { clear: both; }
diff --git a/share/html/NoAuth/css/base/rights-editor.css b/share/html/NoAuth/css/base/rights-editor.css
new file mode 100644
index 0000000..82d7a56
--- /dev/null
+++ b/share/html/NoAuth/css/base/rights-editor.css
@@ -0,0 +1,59 @@
+/* This selector is very heavy handed, but the jQuery UI theme is tenacious */
+.rights-editor, .rights-editor * {
+    font-family: arial, helvetica, sans-serif !important;
+}
+
+/* Styles for putting jQuery UI tabs on the left */
+.rights-editor {
+    border: none;
+    background: transparent;
+}
+
+/* Position and style the left tabs */
+.rights-editor > .ui-tabs-nav {
+    float: left;
+    background: transparent;
+    border: none;
+    color: black;
+}
+
+.rights-editor > .ui-tabs-nav li {
+    float: none;
+    display: block;
+    border: none;
+    background: transparent;
+}
+
+.rights-editor > .ui-tabs-nav li a {
+    float: none;
+    display: block;
+    padding: 0 0 0.2em 1em;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.rights-editor .ui-tabs-nav li.category {
+    text-transform: uppercase;
+}
+
+li.category ~ li.category {
+    margin-top: 1em;
+}
+
+/* Position the outer-most panel */
+.rights-editor > .ui-tabs-panel {
+    position: static;
+    float: right;
+}
+
+.rights-editor .ui-tabs-panel {
+    padding: 2px;
+}
+
+.rights-editor .ui-tabs-panel h3 {
+    margin-top: 0;
+}
+
+.rights-editor ul.rights-list {
+    list-style: none;
+}

commit b9634f3074184c00c22301b9ba720877cc5b02e7
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 30 11:59:33 2010 -0400

    ProcessACLChanges now expects values from a series of checkboxes
    
    This breaks the old SelectRights element, but makes it work for the new
    rights editor.
    
    It now expects form inputs with names like
    SetRights-PrincipalId-ObjType-ObjId instead of Grant/RevokeRight.  Each
    input should be an array listing the rights the principal should have,
    and ProcessACLChanges will modify the current rights to match.
    Additionally, the previously unused CheckACL input listing
    PrincipalId-ObjType-ObjId is now used to catch cases when all the rights
    are removed from a principal and no SetRights input is submitted.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 90e3f3f..5515211 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -1367,25 +1367,30 @@ sub ParseDateToISO {
 
 sub ProcessACLChanges {
     my $ARGSref = shift;
+    my (%state, @results);
 
     #XXX: why don't we get ARGSref like in other Process* subs?
 
-    my @results;
+    my $CheckACL = $ARGSref->{'CheckACL'};
+    my @check = grep { defined } (ref $CheckACL eq 'ARRAY' ? @$CheckACL : $CheckACL);
 
+    # Build our rights state for each Principal-Object tuple
     foreach my $arg ( keys %$ARGSref ) {
-        next unless ( $arg =~ /^(GrantRight|RevokeRight)-(\d+)-(.+?)-(\d+)$/ );
-
-        my ( $method, $principal_id, $object_type, $object_id ) = ( $1, $2, $3, $4 );
+        next unless $arg =~ /^SetRights-(\d+-.+?-\d+)$/;
 
-        my @rights;
-        if ( UNIVERSAL::isa( $ARGSref->{$arg}, 'ARRAY' ) ) {
-            @rights = @{ $ARGSref->{$arg} };
-        } else {
-            @rights = $ARGSref->{$arg};
-        }
-        @rights = grep $_, @rights;
+        my $tuple  = $1;
+        my $value  = $ARGSref->{$arg};
+        my @rights = grep { $_ } (ref $value eq 'ARRAY' ? @$value : $value);
         next unless @rights;
 
+        $state{$tuple} = { map { $_ => 1 } @rights };
+    }
+
+    foreach my $tuple (@check) {
+        next unless $tuple =~ /^(\d+)-(.+?)-(\d+)$/;
+
+        my ( $principal_id, $object_type, $object_id ) = ( $1, $2, $3 );
+
         my $principal = RT::Principal->new( $session{'CurrentUser'} );
         $principal->Load($principal_id);
 
@@ -1405,9 +1410,35 @@ sub ProcessACLChanges {
             next;
         }
 
-        foreach my $right (@rights) {
-            my ( $val, $msg ) = $principal->$method( Object => $obj, Right => $right );
-            push( @results, $msg );
+        my $acls = RT::ACL->new($session{'CurrentUser'});
+        $acls->LimitToObject( $obj );
+        $acls->LimitToPrincipal( Id => $principal_id );
+
+        while ( my $ace = $acls->Next ) {
+            my $right = $ace->RightName;
+
+            # Has right and should have right
+            next if delete $state{$tuple}->{$right};
+
+            # Has right and shouldn't have right
+            my ($val, $msg) = $principal->RevokeRight( Object => $obj, Right => $right );
+            push @results, $msg;
+        }
+
+        # For everything left, they don't have the right but they should
+        for my $right (keys %{ $state{$tuple} || {} }) {
+            delete $state{$tuple}->{$right};
+            my ($val, $msg) = $principal->GrantRight( Object => $obj, Right => $right );
+            push @results, $msg;
+        }
+
+        # Check our state for leftovers
+        if ( keys %{ $state{$tuple} || {} } ) {
+            my $missed = join '|', %{$state{$tuple} || {}};
+            $RT::Logger->warn(
+               "Uh-oh, it looks like we somehow missed a right in "
+              ."ProcessACLChanges.  Here's what was leftover: $missed"
+            );
         }
     }
 
diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
index 25d5586..21d0434 100644
--- a/share/html/Admin/Elements/EditRights
+++ b/share/html/Admin/Elements/EditRights
@@ -71,22 +71,24 @@ for my $category (@$Principals) {
         my $id = "acl-$name-" . $obj->PrincipalId;
         $id =~ s/[^a-zA-Z0-9\-]/_/g;
 </%perl>
+
   <div id="<% $id %>">
     <h3>Rights for <% $loc ? loc($display) : $display %></h3>
     <ul class="rights-list">
 % for my $right (keys %available_rights) {
       <li>
         <input type="checkbox" class="checkbox"
-               name="GrantRight-<% $acldesc %>"
-               id="GrantRight-<% $acldesc %>-<% $right %>"
+               name="SetRights-<% $acldesc %>"
+               id="SetRights-<% $acldesc %>-<% $right %>"
                value="<% $right %>"
                <% $current_rights{$obj->PrincipalId}->{$right} ? 'checked' : '' %> />
-        <label for="GrantRight-<% $acldesc %>-<% $right %>">
+        <label for="SetRights-<% $acldesc %>-<% $right %>">
           <% loc($available_rights{$right}) %>
         </label>
       </li>
 % }
     </ul>
+    <input type="hidden" name="CheckACL" value="<% $acldesc %>" />
   </div>
 <%perl>
     }

commit 8e85e99d08dff694ee166b539d6f3dcb29e1033e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 30 13:50:26 2010 -0400

    Replace SelectRights everywhere with the new rights editor
    
    This introduces GetPrincipalsMap() which consolidates a lot of common
    principal collection creation.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 5515211..9bbee7a 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -2129,6 +2129,58 @@ sub ProcessColumnMapValue {
     return $value;
 }
 
+=head2 GetPrincipalsMap OBJECT, CATEGORIES
+
+Returns an array suitable for passing to /Admin/Elements/EditRights with the
+principal collections mapped from the categories given.
+
+=cut
+
+sub GetPrincipalsMap {
+    my $object = shift;
+    my @map;
+    for (@_) {
+        if (/System/) {
+            my $system = RT::Groups->new($session{'CurrentUser'});
+            $system->LimitToSystemInternalGroups();
+            push @map, ['System' => $system => 'Type' => 1];
+        }
+        elsif (/Groups/) {
+            my $groups = RT::Groups->new($session{'CurrentUser'});
+            $groups->LimitToUserDefinedGroups();
+            # XXX TODO: only find those user groups with rights granted
+            push @map, ['User Groups' => $groups => 'Name' => 0];
+        }
+        elsif (/Roles/) {
+            my $roles = RT::Groups->new($session{'CurrentUser'});
+
+            if ($object->isa('RT::System')) {
+                $roles->LimitToRolesForSystem();
+            }
+            elsif ($object->isa('RT::Queue')) {
+                $roles->LimitToRolesForQueue($object->Id);
+            }
+            else {
+                $RT::Logger->warn("Skipping unknown object type ($object) for Role principals");
+                next;
+            }
+            push @map, ['Roles' => $roles => 'Type' => 1];
+        }
+        elsif (/Users/) {
+            my $Privileged = RT::Group->new($session{'CurrentUser'});
+            $Privileged->LoadSystemInternalGroup('Privileged');
+            my $Users = $Privileged->UserMembersObj();
+            $Users->OrderBy( FIELD => 'Name', ORDER => 'ASC' );
+
+            my $display = sub {
+                $m->scomp('/Elements/ShowUser', User => $_[0], NoEscape => 1)
+            };
+            push @map, ['Users' => $Users => $display => 0];
+        }
+    }
+    return @map;
+}
+
 =head2 _load_container_object ( $type, $id );
 
 Instantiate container object for saving searches.
diff --git a/share/html/Admin/CustomFields/GroupRights.html b/share/html/Admin/CustomFields/GroupRights.html
index 9bb972e..0a31ea7 100644
--- a/share/html/Admin/CustomFields/GroupRights.html
+++ b/share/html/Admin/CustomFields/GroupRights.html
@@ -55,45 +55,11 @@
 
   <form method="post" action="GroupRights.html">
     <input type="hidden" class="hidden" name="id" value="<% $CustomFieldObj->id %>" />
-      
-      
-<h1><&|/l&>System groups</&></h1>
-<table>
-% my $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToSystemInternalGroups();
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% loc($Group->Type) %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object => $CustomFieldObj  &>
-	  </td>
-	</tr>
-% }
-</table>
-<h1><&|/l&>User defined groups</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToUserDefinedGroups();    
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% $Group->Name %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object => $CustomFieldObj  &>
-	  </td>
-	</tr>
-% }
-</table>
-            
-      <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
-      
+
+    <& /Admin/Elements/EditRights, Context => $CustomFieldObj, Principals => \@principals &>
+    <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
   </form>
-  
+
 <%INIT>
 
 if (!defined $id) {
@@ -106,7 +72,9 @@ $CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why => loc("Couldn't l
 my @results = ProcessACLChanges( \%ARGS );
 
 my $title = loc('Modify group rights for custom field [_1]', $CustomFieldObj->Name);
-    
+
+# Principal collections
+my @principals = GetPrincipalsMap($CustomFieldObj, qw(System Groups));
 </%INIT>
 
 <%ARGS>
diff --git a/share/html/Admin/CustomFields/UserRights.html b/share/html/Admin/CustomFields/UserRights.html
index 2d9bc9f..b2c9d67 100644
--- a/share/html/Admin/CustomFields/UserRights.html
+++ b/share/html/Admin/CustomFields/UserRights.html
@@ -53,37 +53,13 @@ Title => $title, &>
 
   <form method="post" action="UserRights.html">
     <input type="hidden" class="hidden" name="id" value="<% $CustomFieldObj->id %>" />
-      
-      
-<table>
-        
-%	while (my $Member = $Users->Next()) {
-% my $UserObj = $Member->MemberObj->Object();
-% my $group = RT::Group->new($session{'CurrentUser'});
-% $group->LoadACLEquivalenceGroup($Member->MemberObj);
-  <tr align="right"> 
-	<td valign="top"><& /Elements/ShowUser, User => $UserObj &></td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId=> $group->PrincipalId,
-        Object => $CustomFieldObj  &>
-	  </td>
-	</tr>
-% }
-      </table>
-            
-      <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
-      
+    <& /Admin/Elements/EditRights, Context => $CustomFieldObj, Principals => \@principals &>
+    <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
   </form>
-  
 <%INIT>
- 
-#Update the acls.
+# Update the acls.
 my @results = ProcessACLChanges( \%ARGS );
 
-# {{{ Deal with setting up the display of current rights.
-
-
-
 if (!defined $id) {
     $m->comp("/Elements/Error", Why => loc("No Class defined"));
 }
@@ -91,20 +67,12 @@ if (!defined $id) {
 my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
 $CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why => loc("Couldn't load Class [_1]",$id));
 
-# Find out which users we want to display ACL selects for
-my $Privileged = RT::Group->new($session{'CurrentUser'});
-$Privileged->LoadSystemInternalGroup('Privileged');
-my $Users = $Privileged->MembersObj();
-
 my $title = loc('Modify user rights for custom field [_1]', $CustomFieldObj->Name);
-  
-# }}}
-    
+
+# Principal collections
+my @principals = GetPrincipalsMap($CustomFieldObj, qw(Users));
 </%INIT>
 
 <%ARGS>
 $id => undef
-$UserString => undef
-$UserOp => undef
-$UserField => undef
 </%ARGS>
diff --git a/share/html/Admin/Elements/SelectRights b/share/html/Admin/Elements/SelectRights
deleted file mode 100755
index c5fe015..0000000
--- a/share/html/Admin/Elements/SelectRights
+++ /dev/null
@@ -1,121 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%# 
-%# COPYRIGHT:
-%# 
-%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
-%#                                          <jesse at bestpractical.com>
-%# 
-%# (Except where explicitly superseded by other copyright notices)
-%# 
-%# 
-%# LICENSE:
-%# 
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%# 
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-%# General Public License for more details.
-%# 
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%# 
-%# 
-%# CONTRIBUTION SUBMISSION POLICY:
-%# 
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%# 
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%# 
-%# END BPS TAGGED BLOCK }}}
-<input type="hidden" class="hidden" name="CheckACL"  value="<%$ACLDesc%>" />
-     <table border="0">
-<tr>
-<td valign="top" width="180" align="left">
-<%PERL>
-my %current_rights;
-my @pairs;
-while ( my $ace = $ACLObj->Next ) {
-    my $right = $ace->RightName;
-    $current_rights{ $right } = 1;
-    push @pairs, [$right, loc($right)];
-}
- at pairs = sort { $a->[1] cmp $b->[1] } @pairs;
-</%PERL>
-<h3><&|/l&>Current rights</&></h3>
-% unless ( @pairs ) {
-<i><&|/l&>No rights granted.</&></i> <br />
-% } else {
-<i>(<&|/l&>Check box to revoke right</&>)</i><br />
-% foreach my $pair ( @pairs ) {
-<input type="checkbox" class="checkbox" value="<% $pair->[0] %>" name="RevokeRight-<% $ACLDesc %>" />&nbsp;<% $pair->[1] %><br />
-% } }
-</td>
-<td valign="top">
-<h3><&|/l&>New rights</&></h3> 
-<select size="5" multiple="multiple" name="GrantRight-<%$ACLDesc%>">
-% foreach my $pair (sort { $a->[1] cmp $b->[1] } map [$_, loc($_)], grep !$current_rights{$_}, keys %Rights) {
-      <option value="<% $pair->[0] %>" title="<% loc($Rights{$pair->[0]}) %>"><% $pair->[1] %></option>
-% }
-<option value="" selected="selected"><&|/l&>(no value)</&></option>
-</select>
-</td>
-</tr>
-</table>
-<%INIT>
-    my ($ACLDesc, $AppliesTo, %Rights);
-
-    # if the principal id points to a user, we really want to point
-    # to their ACL equivalence group. The machinations we're going through
-    # lead me to start to suspect that we really want users and groups
-    # to just be the same table. or _maybe_ that we want an object db.
-    my $princ = RT::Principal->new($RT::SystemUser);
-    $princ->Load($PrincipalId);
-    if ($princ->PrincipalType eq 'User') {
-    my $group = RT::Group->new($RT::SystemUser);
-        $group->LoadACLEquivalenceGroup($princ);
-        $PrincipalId = $group->PrincipalId;
-    }
-
-
-    my $ACLObj = RT::ACL->new($session{'CurrentUser'});
-    my $ACE = RT::ACE->new($session{'CurrentUser'});
-
-
-    $ACLObj->LimitToObject( $Object);
-    $ACLObj->LimitToPrincipal( Id => $PrincipalId);
-    $ACLObj->OrderBy(FIELD=>'RightName'); 
-
-    if (ref($Object) && UNIVERSAL::can($Object, 'AvailableRights')) { 
-        %Rights = %{$Object->AvailableRights};
-    } 
-
-        else {
-                %Rights = ( loc('System Error') => loc("No rights found") );
-        }
-        
-    $ACLDesc = "$PrincipalId-".ref($Object)."-".$Object->Id;
-</%INIT>
-    
-<%ARGS>
-$PrincipalType => undef
-$PrincipalId => undef
-$Object =>undef
-</%ARGS>
diff --git a/share/html/Admin/Global/GroupRights.html b/share/html/Admin/Global/GroupRights.html
index c9daf13..31941e9 100755
--- a/share/html/Admin/Global/GroupRights.html
+++ b/share/html/Admin/Global/GroupRights.html
@@ -51,73 +51,17 @@
     Title => loc('Modify global group rights') &>  
 <& /Elements/ListActions, actions => \@results &>
 
-  <form method="post" action="GroupRights.html">
-      
-<&| /Widgets/TitleBox, title => loc('Modify global group rights.')&>
-      
-<h1><&|/l&>System groups</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToSystemInternalGroups();
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% loc($Group->Type) %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object  =>$RT::System &>
-	  </td>
-	</tr>
-% }
-</table>
-<h1><&|/l&>Roles</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToRolesForSystem();
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% loc($Group->Type) %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object  => $RT::System &>
-	  </td>
-	</tr>
-% }
-</table>
-<h1><&|/l&>User defined groups</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToUserDefinedGroups();    
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% $Group->Name %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        Object  => $RT::System &>
-	  </td>
-	</tr>
-% }
-</table>
-            
-      </&>
-      <& /Elements/Submit, Label => loc('Modify Group Rights'), Reset => 1 &>
-      
-  </form>
+<form method="post" action="GroupRights.html">
+  <& /Admin/Elements/EditRights, Context => $RT::System, Principals => \@principals &>
+  <& /Elements/Submit, Label => loc('Modify Group Rights'), Reset => 1 &>
+</form>
   
 <%INIT>
- 
-  #Update the acls.
-  my @results =  ProcessACLChanges(\%ARGS);
+# Update the acls.
+my @results = ProcessACLChanges(\%ARGS);
 
-
-my $Groups;
-    
+# Principal collections
+my @principals = GetPrincipalsMap($RT::System, qw(System Roles Groups));
 </%INIT>
-
 <%ARGS>
 </%ARGS>
diff --git a/share/html/Admin/Global/UserRights.html b/share/html/Admin/Global/UserRights.html
index 2242774..ac2a42c 100755
--- a/share/html/Admin/Global/UserRights.html
+++ b/share/html/Admin/Global/UserRights.html
@@ -51,49 +51,12 @@
     Title => loc('Modify global user rights') &>  
 <& /Elements/ListActions, actions => \@results &>
 
-  <form method="post" action="UserRights.html">
-      
-<&| /Widgets/TitleBox, title => loc('Modify global user rights.') &>
-<table>
-
-% while ( my $UserObj = $Users->Next ) {
-% my $group = RT::Group->new($session{'CurrentUser'});
-% $group->LoadACLEquivalenceGroup( $UserObj );
-  <tr align="right">
-	<td valign="top"><& /Elements/ShowUser, User => $UserObj &></td>
-	<td><& /Admin/Elements/SelectRights,
-        PrincipalId => $group->PrincipalId,
-        Object => $RT::System,
-    &></td>
-  </tr>
-% }
-</table>
-</&>
-
-<& /Elements/Submit, Label => loc('Modify User Rights'), Reset => 1 &>
-      
+<form method="post" action="UserRights.html">
+  <& /Admin/Elements/EditRights, Context => $RT::System, Principals => \@principals &>
+  <& /Elements/Submit, Label => loc('Modify User Rights'), Reset => 1 &>
 </form>
 <%INIT>
- 
-  #Update the acls.
-  my @results =  ProcessACLChanges(\%ARGS);
-
-# {{{ Deal with setting up the display of current rights.
-
-
-# Find out which users we want to display ACL selects for
-my $Privileged = RT::Group->new($session{'CurrentUser'});
-$Privileged->LoadSystemInternalGroup('Privileged');
-my $Users = $Privileged->UserMembersObj();
-$Users->OrderBy( FIELD => $UserOrderBy, ORDER => $UserOrder );
-
-    
-  
-# }}}
-    
+# Update the acls.
+my @results = ProcessACLChanges(\%ARGS);
+my @principals = GetPrincipalsMap($RT::System, 'Users');
 </%INIT>
-
-<%ARGS>
-$UserOrderBy => 'Name'
-$UserOrder => 'ASC'
-</%ARGS>
diff --git a/share/html/Admin/Groups/GroupRights.html b/share/html/Admin/Groups/GroupRights.html
index df834a8..4311ee4 100755
--- a/share/html/Admin/Groups/GroupRights.html
+++ b/share/html/Admin/Groups/GroupRights.html
@@ -54,54 +54,12 @@
 
   <form method="post" action="GroupRights.html">
     <input type="hidden" class="hidden" name="id" value="<% $GroupObj->id %>" />
-      
-<&| /Widgets/TitleBox, title => loc('Modify group rights for group [_1]', $GroupObj->Name) &>
-      
-<h1><&|/l&>System groups</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToSystemInternalGroups();
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% loc($Group->Type) %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        PrincipalType => 'Group',
-        Object => $GroupObj  &>
-	  </td>
-	</tr>
-% }
-</table>
-<h1><&|/l&>User defined groups</&></h1>
-<table>
-% $Groups = RT::Groups->new($session{'CurrentUser'});
-% $Groups->LimitToUserDefinedGroups();    
-%	while (my $Group = $Groups->Next()) {
-  <tr align="right"> 
-	<td valign="top">
-	    <% $Group->Name %>
-		  </td>
-	  <td>
-	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
-        PrincipalType => 'Group',
-        Object => $GroupObj  &>
-	  </td>
-	</tr>
-% }
-</table>
-            
-      </&>
-      <& /Elements/Submit, Label => loc('Modify Group Rights'), Reset => 1 &>
-      
+    <& /Admin/Elements/EditRights, Context => $GroupObj, Principals => \@principals &>
+    <& /Elements/Submit, Label => loc('Modify Group Rights'), Reset => 1 &>
   </form>
-  
 <%INIT>
- 
-  #Update the acls.
-  my @results =  ProcessACLChanges(\%ARGS);
-
+# Update the acls.
+my @results = ProcessACLChanges(\%ARGS);
 
 if (!defined $id) {
     Abort(loc("No Group defined"));
@@ -110,8 +68,7 @@ if (!defined $id) {
 my $GroupObj = RT::Group->new($session{'CurrentUser'});
 $GroupObj->Load($id) || Abort(loc("Couldn't load group [_1]",$id));
 
-my $Groups;
-    
+my @principals = GetPrincipalsMap($GroupObj, 'System', 'User Groups');
 </%INIT>
 
 <%ARGS>
diff --git a/share/html/Admin/Groups/UserRights.html b/share/html/Admin/Groups/UserRights.html
index e930fef..31dae23 100755
--- a/share/html/Admin/Groups/UserRights.html
+++ b/share/html/Admin/Groups/UserRights.html
@@ -54,41 +54,13 @@
 
   <form method="post" action="UserRights.html">
     <input type="hidden" class="hidden" name="id" value="<% $GroupObj->id %>" />
-      
-<&| /Widgets/TitleBox, title => loc('Modify user rights for group [_1]', $GroupObj->Name) &>
-<table>
-% while ( my $Member = $Users->Next ) {
-% my $UserObj = $Member->MemberObj->Object;
-  <tr align="right">
-      <td valign="top">
-          <a href="<% RT->Config->Get('WebPath') %>/Admin/Users/Modify.html?id=<% $UserObj->id %>">
-              <& /Elements/ShowUser, User => $UserObj &>
-          </a>
-      </td>
-    <td><& /Admin/Elements/SelectRights,
-        PrincipalId => $Member->MemberObj->Id,
-        PrincipalType => 'User',
-        Object => $GroupObj,
-    &></td>
-  </tr>
-% }
-</table>
-</&>
-
-<& /Elements/Submit, Label => loc('Modify User Rights'), Reset => 1 &>
-
-</form>
+    <& /Admin/Elements/EditRights, Context => $GroupObj, Principals => \@principals &>
+    <& /Elements/Submit, Label => loc('Modify User Rights'), Reset => 1 &>
+  </form>
 
 <%INIT>
- 
-  #Update the acls.
-  my @results =  ProcessACLChanges(\%ARGS);
-
-# {{{ Deal with setting up the display of current rights.
-
-
-#Define vars used in html above
-
+# Update the acls.
+my @results = ProcessACLChanges(\%ARGS);
 
 if (!defined $id) {
     Abort(loc("No Group defined"));
@@ -97,20 +69,9 @@ if (!defined $id) {
 my $GroupObj = RT::Group->new($session{'CurrentUser'});
 $GroupObj->Load($id) || Abort(loc("Couldn't load group [_1]",$id));
 
-# Find out which users we want to display ACL selects for
-my $Privileged = RT::Group->new($session{'CurrentUser'});
-$Privileged->LoadSystemInternalGroup('Privileged');
-my $Users = $Privileged->MembersObj();
-
-    
-  
-# }}}
-    
+my @principals = GetPrincipalsMap($GroupObj, 'Users');
 </%INIT>
 
 <%ARGS>
 $id => undef
-$UserString => undef
-$UserOp => undef
-$UserField => undef
 </%ARGS>
diff --git a/share/html/Admin/Queues/GroupRights.html b/share/html/Admin/Queues/GroupRights.html
index 2f5824f..4daa61d 100755
--- a/share/html/Admin/Queues/GroupRights.html
+++ b/share/html/Admin/Queues/GroupRights.html
@@ -55,8 +55,7 @@
 <form method="post" action="GroupRights.html">
   <input type="hidden" class="hidden" name="id" value="<% $QueueObj->id %>" />
 
-%# XXX TODO: this was just after the opening table tag, put it somewhere reasonable    
-% $m->callback( %ARGS, QueueObj => $QueueObj, results => \@results );
+% $m->callback( %ARGS, QueueObj => $QueueObj, results => \@results, principals => \@principals );
 
   <& /Admin/Elements/EditRights, Context => $QueueObj, Principals => \@principals &>
 
@@ -65,7 +64,7 @@
 
 <%INIT>
 # Update the acls.
-my @results =  ProcessACLChanges(\%ARGS);
+my @results = ProcessACLChanges(\%ARGS);
 
 if (!defined $id) {
     Abort(loc("No Queue defined"));
@@ -77,22 +76,7 @@ $QueueObj->Load($id) || Abort(loc("Couldn't load queue [_1]",$id));
 my $current_tab = 'Admin/Queues/GroupRights.html?id='.$QueueObj->id;
 
 # Principal collections
-my $system = RT::Groups->new($session{'CurrentUser'});
-$system->LimitToSystemInternalGroups();
-
-my $roles = RT::Groups->new($session{'CurrentUser'});
-$roles->LimitToRolesForQueue($QueueObj->Id);
-
-my $groups = RT::Groups->new($session{'CurrentUser'});
-$groups->LimitToUserDefinedGroups();
-# XXX TODO: only find those user groups with rights granted
-
-my @principals = (
-    # Category        collection   column    loc?
-    ['System'      => $system   => 'Type' => 1],
-    ['Roles'       => $roles    => 'Type' => 1],
-    ['User Groups' => $groups   => 'Name' => 0],
-);
+my @principals = GetPrincipalsMap($QueueObj, qw(System Roles Groups));
 </%INIT>
 <%ARGS>
 $id => undef
diff --git a/share/html/Admin/Queues/UserRights.html b/share/html/Admin/Queues/UserRights.html
index 757991f..75c3f9a 100755
--- a/share/html/Admin/Queues/UserRights.html
+++ b/share/html/Admin/Queues/UserRights.html
@@ -75,15 +75,7 @@ if (!defined $id) {
 my $QueueObj = RT::Queue->new($session{'CurrentUser'});
 $QueueObj->Load($id) || Abort(loc("Couldn't load queue [_1]",$id));
 
-# Find out which users we want to display ACLs for
-my $Privileged = RT::Group->new($session{'CurrentUser'});
-$Privileged->LoadSystemInternalGroup('Privileged');
-my $Users = $Privileged->UserMembersObj();
-
-my $display = sub {
-    $m->scomp('/Elements/ShowUser', User => $_[0], NoEscape => 1)
-};
-my @principals = (['Users' => $Users => $display => 0]);
+my @principals = GetPrincipalsMap($QueueObj, 'Users');
 
 my $current_tab = 'Admin/Queues/UserRights.html?id='.$QueueObj->id;
 </%INIT>

commit 71928032bf0ac78b9a139b0a163b3af901287e28
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 30 15:36:05 2010 -0400

    Limit users and groups to those currently granted rights
    
    Also add order by clauses to all the queries for consistency

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 9bbee7a..780b85e 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -2143,12 +2143,21 @@ sub GetPrincipalsMap {
         if (/System/) {
             my $system = RT::Groups->new($session{'CurrentUser'});
             $system->LimitToSystemInternalGroups();
+            $system->OrderBy( FIELD => 'Type', ORDER => 'ASC' );
             push @map, ['System' => $system => 'Type' => 1];
         }
         elsif (/Groups/) {
             my $groups = RT::Groups->new($session{'CurrentUser'});
             $groups->LimitToUserDefinedGroups();
-            # XXX TODO: only find those user groups with rights granted
+            $groups->OrderBy( FIELD => 'Name', ORDER => 'ASC' );
+
+            # Only show groups who have rights granted on this object
+            $groups->WithGroupRight(
+                Right   => '',
+                Object  => $object,
+                IncludeSystemRights => 0
+            );
+
             push @map, ['User Groups' => $groups => 'Name' => 0];
         }
         elsif (/Roles/) {
@@ -2164,6 +2173,7 @@ sub GetPrincipalsMap {
                 $RT::Logger->warn("Skipping unknown object type ($object) for Role principals");
                 next;
             }
+            $roles->OrderBy( FIELD => 'Type', ORDER => 'ASC' );
             push @map, ['Roles' => $roles => 'Type' => 1];
         }
         elsif (/Users/) {
@@ -2172,6 +2182,13 @@ sub GetPrincipalsMap {
             my $Users = $Privileged->UserMembersObj();
             $Users->OrderBy( FIELD => 'Name', ORDER => 'ASC' );
 
+            # Only show users who have rights granted on this object
+            $Users->WhoHaveGroupRight(
+                Right   => '',
+                Object  => $object,
+                IncludeSystemRights => 0
+            );
+
             my $display = sub {
                 $m->scomp('/Elements/ShowUser', User => $_[0], NoEscape => 1)
             };

commit 6d74fdb24706cc53fd5367f9d084d1bfcf77433c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 30 15:40:40 2010 -0400

    Sort by right name and make it a tooltip for reference

diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
index 21d0434..dc9839b 100644
--- a/share/html/Admin/Elements/EditRights
+++ b/share/html/Admin/Elements/EditRights
@@ -53,7 +53,6 @@ for my $collection (map { $_->[1] } @$Principals) {
         my $acls = RT::ACL->new($session{'CurrentUser'});
         $acls->LimitToObject( $Context );
         $acls->LimitToPrincipal( Id => $group->PrincipalId );
-        $acls->OrderBy( FIELD => 'RightName' ); 
 
         while ( my $ace = $acls->Next ) {
             my $right = $ace->RightName;
@@ -75,14 +74,14 @@ for my $category (@$Principals) {
   <div id="<% $id %>">
     <h3>Rights for <% $loc ? loc($display) : $display %></h3>
     <ul class="rights-list">
-% for my $right (keys %available_rights) {
+% for my $right (sort keys %available_rights) {
       <li>
         <input type="checkbox" class="checkbox"
                name="SetRights-<% $acldesc %>"
                id="SetRights-<% $acldesc %>-<% $right %>"
                value="<% $right %>"
                <% $current_rights{$obj->PrincipalId}->{$right} ? 'checked' : '' %> />
-        <label for="SetRights-<% $acldesc %>-<% $right %>">
+        <label for="SetRights-<% $acldesc %>-<% $right %>" title="<% $right %>">
           <% loc($available_rights{$right}) %>
         </label>
       </li>

commit a2f018cba22512c4bc22930446d557f5de2c6d4e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 30 17:25:30 2010 -0400

    Fix principal map group name

diff --git a/share/html/Admin/Groups/GroupRights.html b/share/html/Admin/Groups/GroupRights.html
index 4311ee4..68382e9 100755
--- a/share/html/Admin/Groups/GroupRights.html
+++ b/share/html/Admin/Groups/GroupRights.html
@@ -68,7 +68,7 @@ if (!defined $id) {
 my $GroupObj = RT::Group->new($session{'CurrentUser'});
 $GroupObj->Load($id) || Abort(loc("Couldn't load group [_1]",$id));
 
-my @principals = GetPrincipalsMap($GroupObj, 'System', 'User Groups');
+my @principals = GetPrincipalsMap($GroupObj, 'System', 'Groups');
 </%INIT>
 
 <%ARGS>

commit d4b552a6c5415ade24c68a0cecffb8f527c3bfdc
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 30 17:26:13 2010 -0400

    Basic framework for adding rights to a new principal (users, groups)
    
    This ability is required since now we only show users/groups with rights
    granted.
    
    Left todo for this feature: group autocomplete and actual handling in
    ProcessACLChanges.  User autocomplete should be restricted to
    Privileged.

diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
index dc9839b..8fb5bf0 100644
--- a/share/html/Admin/Elements/EditRights
+++ b/share/html/Admin/Elements/EditRights
@@ -1,9 +1,20 @@
 <%args>
 $Context
 $Principals
+$AddPrincipal => undef
 </%args>
 <%init>
 use Scalar::Util qw(blessed);
+
+unless ( $AddPrincipal ) {
+    my $last = $Principals->[-1];
+    if ( $last->[0] =~ /Groups/i ) {
+        $AddPrincipal = 'group';
+    }
+    elsif ( $last->[0] =~ /Users/i ) {
+        $AddPrincipal = 'user';
+    }
+}
 </%init>
 %# Principals is an array of arrays, where the inner arrays are like:
 %#      [ 'Category name' => $CollectionObj => 'DisplayColumn' => 1 ]
@@ -34,6 +45,16 @@ for my $category (@$Principals) {
     }
 }
 </%perl>
+% if ( $AddPrincipal ) {
+    <li class="category"><&|/l&>Add</&> <% loc($AddPrincipal) %></li>
+    <li>
+      <a href="#acl-addprincipal">
+        <input type="text" value=""
+               name="AddPrincipalForRights-<% lc $AddPrincipal %>"
+               id="AddPrincipalForRights-<% lc $AddPrincipal %>" />
+      </a>
+    </li>
+% }
   </ul>
 
 <%perl>
@@ -72,7 +93,7 @@ for my $category (@$Principals) {
 </%perl>
 
   <div id="<% $id %>">
-    <h3>Rights for <% $loc ? loc($display) : $display %></h3>
+    <h3><&|/l&>Rights for</&> <% $loc ? loc($display) : $display %></h3>
     <ul class="rights-list">
 % for my $right (sort keys %available_rights) {
       <li>
@@ -92,5 +113,25 @@ for my $category (@$Principals) {
 <%perl>
     }
 }
+
+if ( $AddPrincipal ) {
+    my $acldesc = join '-', 'addprincipal', ref($Context), $Context->Id;
 </%perl>
+  <div id="acl-addprincipal">
+    <h3><&|/l&>Add rights for this</&> <% loc($AddPrincipal) %></h3>
+    <ul class="rights-list">
+% for my $right (sort keys %available_rights) {
+      <li>
+        <input type="checkbox" class="checkbox"
+               name="SetRights-<% $acldesc %>"
+               id="SetRights-<% $acldesc %>-<% $right %>"
+               value="<% $right %>" />
+        <label for="SetRights-<% $acldesc %>-<% $right %>" title="<% $right %>">
+          <% loc($available_rights{$right}) %>
+        </label>
+      </li>
+% }
+    </ul>
+  </div>
+% }
 </div>
diff --git a/share/html/NoAuth/js/userautocomplete.js b/share/html/NoAuth/js/userautocomplete.js
index b296d0c..8944f5c 100644
--- a/share/html/NoAuth/js/userautocomplete.js
+++ b/share/html/NoAuth/js/userautocomplete.js
@@ -3,7 +3,7 @@ jQuery(function() {
     var multipleCompletion = new Array("Requestors", "To", "Bcc", "Cc", "AdminCc", "WatcherAddressEmail[123]", "UpdateCc", "UpdateBcc");
 
     // inputs with only a single email address allowed
-    var singleCompletion   = new Array("(Add|Delete)Requestor", "(Add|Delete)Cc", "(Add|Delete)AdminCc");
+    var singleCompletion   = new Array("(Add|Delete)Requestor", "(Add|Delete)Cc", "(Add|Delete)AdminCc", "AddPrincipalForRights-user");
 
     // build up the regexps we'll use to match
     var applyto  = new RegExp('^(' + multipleCompletion.concat(singleCompletion).join('|') + ')$');

commit 8e43a69bfd68427766376a3682c902f198e7161b
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Sep 2 12:23:43 2010 -0400

    Only autocomplete privileged users for the rights editor

diff --git a/share/html/Helpers/Autocomplete/Users b/share/html/Helpers/Autocomplete/Users
index b4046a5..5aecf07 100644
--- a/share/html/Helpers/Autocomplete/Users
+++ b/share/html/Helpers/Autocomplete/Users
@@ -53,6 +53,7 @@ $return => ''
 $term => undef
 $delim => undef
 $max => 10
+$privileged => undef
 </%ARGS>
 <%INIT>
 require JSON;
@@ -89,6 +90,8 @@ my %fields = %{ RT->Config->Get('UserAutocompleteFields')
 my $users = RT::Users->new( $CurrentUser );
 $users->RowsPerPage( $max );
 
+$users->LimitToPrivileged() if $privileged;
+
 while (my ($name, $op) = each %fields) {
     $op = 'STARTSWITH'
         unless $op =~ /^(?:LIKE|(?:START|END)SWITH)$/i;
diff --git a/share/html/NoAuth/js/userautocomplete.js b/share/html/NoAuth/js/userautocomplete.js
index 8944f5c..a1a799f 100644
--- a/share/html/NoAuth/js/userautocomplete.js
+++ b/share/html/NoAuth/js/userautocomplete.js
@@ -3,11 +3,15 @@ jQuery(function() {
     var multipleCompletion = new Array("Requestors", "To", "Bcc", "Cc", "AdminCc", "WatcherAddressEmail[123]", "UpdateCc", "UpdateBcc");
 
     // inputs with only a single email address allowed
-    var singleCompletion   = new Array("(Add|Delete)Requestor", "(Add|Delete)Cc", "(Add|Delete)AdminCc", "AddPrincipalForRights-user");
+    var singleCompletion   = new Array("(Add|Delete)Requestor", "(Add|Delete)Cc", "(Add|Delete)AdminCc");
+
+    // inputs for only privileged users
+    var privilegedCompletion = new Array("AddPrincipalForRights-user");
 
     // build up the regexps we'll use to match
-    var applyto  = new RegExp('^(' + multipleCompletion.concat(singleCompletion).join('|') + ')$');
+    var applyto  = new RegExp('^(' + multipleCompletion.concat(singleCompletion, privilegedCompletion).join('|') + ')$');
     var acceptsMultiple = new RegExp('^(' + multipleCompletion.join('|') + ')$');
+    var onlyPrivileged = new RegExp('^(' + privilegedCompletion.join('|') + ')$');
 
     var inputs = document.getElementsByTagName("input");
 
@@ -22,8 +26,14 @@ jQuery(function() {
             source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users"
         };
 
+        var queryargs = [];
+
+        if (inputName.match(onlyPrivileged)) {
+            queryargs.push("privileged=1");
+        }
+
         if (inputName.match(acceptsMultiple)) {
-            options.source = options.source + "?delim=,";
+            queryargs.push("delim=,");
 
             options.focus = function () {
                 // prevent value inserted on focus
@@ -38,6 +48,10 @@ jQuery(function() {
                 return false;
             }
         }
+
+        if (queryargs.length)
+            options.source += "?" + queryargs.join("&");
+
         jQuery(input).autocomplete(options);
     }
 });

commit b70294586642168f126bede4b3e715f55189e74a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Sep 2 13:27:21 2010 -0400

    Update ProcessACLChanges to deal with adding rights to new principals

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 780b85e..6d6014b 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -1374,6 +1374,37 @@ sub ProcessACLChanges {
     my $CheckACL = $ARGSref->{'CheckACL'};
     my @check = grep { defined } (ref $CheckACL eq 'ARRAY' ? @$CheckACL : $CheckACL);
 
+    # Check if we want to grant rights to a previously rights-less user
+    for my $type (qw(user group)) {
+        my $key = "AddPrincipalForRights-$type";
+
+        next unless $ARGSref->{$key};
+
+        my $principal;
+        if ( $type eq 'user' ) {
+            $principal = RT::User->new( $session{'CurrentUser'} );
+            $principal->Load( $ARGSref->{$key} );
+        }
+        else {
+            $principal = RT::Group->new( $session{'CurrentUser'} );
+            $principal->LoadUserDefinedGroup( $ARGSref->{$key} );
+        }
+
+        unless ($principal->PrincipalId) {
+            push @results, loc("Couldn't load the specified principal");
+            next;
+        }
+
+        my $principal_id = $principal->PrincipalId;
+
+        # Turn our addprincipal rights spec into a real one
+        for my $arg (keys %$ARGSref) {
+            next unless $arg =~ /^SetRights-addprincipal-(.+?-\d+)$/;
+            $ARGSref->{"SetRights-$principal_id-$1"} = $ARGSref->{$arg};
+            push @check, "$principal_id-$1";
+        }
+    }
+
     # Build our rights state for each Principal-Object tuple
     foreach my $arg ( keys %$ARGSref ) {
         next unless $arg =~ /^SetRights-(\d+-.+?-\d+)$/;
diff --git a/share/html/NoAuth/js/userautocomplete.js b/share/html/NoAuth/js/userautocomplete.js
index a1a799f..cc74868 100644
--- a/share/html/NoAuth/js/userautocomplete.js
+++ b/share/html/NoAuth/js/userautocomplete.js
@@ -28,6 +28,9 @@ jQuery(function() {
 
         var queryargs = [];
 
+        if (inputName.match("AddPrincipalForRights-user"))
+            queryargs.push("return=Name");
+
         if (inputName.match(onlyPrivileged)) {
             queryargs.push("privileged=1");
         }

commit c688bf7e5765a7f3f7364b0e5f3daca80a271abf
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Sep 2 13:27:55 2010 -0400

    Don't include subgroup members when finding user groups with rights

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 6d6014b..c20f675 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -2186,7 +2186,8 @@ sub GetPrincipalsMap {
             $groups->WithGroupRight(
                 Right   => '',
                 Object  => $object,
-                IncludeSystemRights => 0
+                IncludeSystemRights => 0,
+                IncludeSubgroupMembers => 0,
             );
 
             push @map, ['User Groups' => $groups => 'Name' => 0];

commit 23162aa0f996c7d763a5e051f3c1e9c3c9796781
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Sep 2 13:50:20 2010 -0400

    Add group autocomplete for the new rights editor

diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
index 8fb5bf0..0878f3b 100644
--- a/share/html/Admin/Elements/EditRights
+++ b/share/html/Admin/Elements/EditRights
@@ -6,6 +6,7 @@ $AddPrincipal => undef
 <%init>
 use Scalar::Util qw(blessed);
 
+# Try to detect if we want to include an add user/group box
 unless ( $AddPrincipal ) {
     my $last = $Principals->[-1];
     if ( $last->[0] =~ /Groups/i ) {
@@ -52,6 +53,13 @@ for my $category (@$Principals) {
         <input type="text" value=""
                name="AddPrincipalForRights-<% lc $AddPrincipal %>"
                id="AddPrincipalForRights-<% lc $AddPrincipal %>" />
+% if (lc $AddPrincipal eq 'group') {
+        <script type="text/javascript">
+            jQuery("#AddPrincipalForRights-<% lc $AddPrincipal %>").autocomplete({
+                source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Groups",
+            });
+        </script>
+% }
       </a>
     </li>
 % }
diff --git a/share/html/Helpers/Autocomplete/Groups b/share/html/Helpers/Autocomplete/Groups
new file mode 100644
index 0000000..0c932a7
--- /dev/null
+++ b/share/html/Helpers/Autocomplete/Groups
@@ -0,0 +1,80 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%# 
+%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
+%#                                          <jesse at bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+
+<% JSON::to_json( \@suggestions ) |n %>
+% $m->abort;
+<%ARGS>
+$term => undef
+$max => 10
+</%ARGS>
+<%INIT>
+require JSON;
+
+$m->abort unless defined $term
+             and length $term;
+
+my $CurrentUser = $session{'CurrentUser'};
+
+# Require privileged users
+$m->abort unless $CurrentUser->Privileged;
+
+my $groups = RT::Groups->new( $CurrentUser );
+$groups->RowsPerPage( $max );
+$groups->LimitToUserDefinedGroups();
+$groups->Limit(
+    FIELD           => 'Name',
+    OPERATOR        => 'LIKE',
+    VALUE           => $term,
+);
+
+my @suggestions;
+
+while ( my $group = $groups->Next ) {
+    push @suggestions, $group->Name;
+}
+</%INIT>

commit ec2817c0918e961e3ea92c240a88d7fde3b4243f
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Sep 2 17:05:45 2010 -0400

    Lump rights into 3 categories for display in the rights editor

diff --git a/lib/RT/CustomField_Overlay.pm b/lib/RT/CustomField_Overlay.pm
index d35b2a6..2cceb76 100755
--- a/lib/RT/CustomField_Overlay.pm
+++ b/lib/RT/CustomField_Overlay.pm
@@ -170,6 +170,13 @@ our $RIGHTS = {
     ModifyCustomField         => 'Add, delete and modify custom field values for objects' #loc_pair
 };
 
+our $RIGHT_CATEGORIES = {
+    SeeCustomField          => 'General',
+    AdminCustomField        => 'Admin',
+    AdminCustomFieldValues  => 'Admin',
+    ModifyCustomField       => 'Staff',
+};
+
 # Tell RT::ACE that this sort of object can get acls granted
 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
 
@@ -195,6 +202,17 @@ sub AvailableRights {
     return $RIGHTS;
 }
 
+=head2 RightCategories
+
+Returns a hashref where the keys are rights for this type of object and the
+values are the category (General, Staff, Admin) the right falls into.
+
+=cut
+
+sub RightCategories {
+    return $RIGHT_CATEGORIES;
+}
+
 =head1 NAME
 
   RT::CustomField_Overlay - overlay for RT::CustomField
diff --git a/lib/RT/Dashboard.pm b/lib/RT/Dashboard.pm
index 401933a..aa0f4fb 100644
--- a/lib/RT/Dashboard.pm
+++ b/lib/RT/Dashboard.pm
@@ -88,6 +88,19 @@ RT::System::AddRights(
     DeleteOwnDashboard => 'Delete personal dashboards', #loc_pair
 );
 
+RT::System::AddRightCategories(
+    SubscribeDashboard => 'Staff',
+
+    SeeDashboard       => 'General',
+    CreateDashboard    => 'Admin',
+    ModifyDashboard    => 'Admin',
+    DeleteDashboard    => 'Admin',
+
+    SeeOwnDashboard    => 'Staff',
+    CreateOwnDashboard => 'Staff',
+    ModifyOwnDashboard => 'Staff',
+    DeleteOwnDashboard => 'Staff',
+);
 
 =head2 ObjectName
 
diff --git a/lib/RT/Group_Overlay.pm b/lib/RT/Group_Overlay.pm
index 12141f9..603fd35 100755
--- a/lib/RT/Group_Overlay.pm
+++ b/lib/RT/Group_Overlay.pm
@@ -81,7 +81,7 @@ use RT::GroupMembers;
 use RT::Principals;
 use RT::ACL;
 
-use vars qw/$RIGHTS/;
+use vars qw/$RIGHTS $RIGHT_CATEGORIES/;
 
 $RIGHTS = {
     AdminGroup           => 'Modify group metadata or delete group',  # loc_pair
@@ -100,6 +100,20 @@ $RIGHTS = {
     DeleteGroupDashboard    => 'Delete dashboards for this group', #loc_pair
 };
 
+$RIGHT_CATEGORIES = {
+    AdminGroup              => 'Admin',
+    AdminGroupMembership    => 'Admin',
+    DelegateRights          => 'Staff',
+    ModifyOwnMembership     => 'Staff',
+    EditSavedSearches       => 'Admin',
+    ShowSavedSearches       => 'Staff',
+    SeeGroup                => 'Staff',
+    SeeGroupDashboard       => 'Staff',
+    CreateGroupDashboard    => 'Admin',
+    ModifyGroupDashboard    => 'Admin',
+    DeleteGroupDashboard    => 'Admin',
+};
+
 # Tell RT::ACE that this sort of object can get acls granted
 $RT::ACE::OBJECT_TYPES{'RT::Group'} = 1;
 
@@ -137,6 +151,17 @@ sub AvailableRights {
     return($RIGHTS);
 }
 
+=head2 RightCategories
+
+Returns a hashref where the keys are rights for this type of object and the
+values are the category (General, Staff, Admin) the right falls into.
+
+=cut
+
+sub RightCategories {
+    return $RIGHT_CATEGORIES;
+}
+
 
 # {{{ sub SelfDescription
 
diff --git a/lib/RT/Queue_Overlay.pm b/lib/RT/Queue_Overlay.pm
index cd039e8..cf655d0 100755
--- a/lib/RT/Queue_Overlay.pm
+++ b/lib/RT/Queue_Overlay.pm
@@ -119,6 +119,37 @@ our $RIGHTS = {
 
 };
 
+our $RIGHT_CATEGORIES = {
+    SeeQueue            => 'General',
+    AdminQueue          => 'Admin',
+    ShowACL             => 'Admin',
+    ModifyACL           => 'Admin',
+    ModifyQueueWatchers => 'Admin',
+    SeeCustomField      => 'General',
+    ModifyCustomField   => 'Staff',
+    AssignCustomFields  => 'Admin',
+    ModifyTemplate      => 'Admin',
+    ShowTemplate        => 'Admin',
+    ModifyScrips        => 'Admin',
+    ShowScrips          => 'Admin',
+    ShowTicket          => 'General',
+    ShowTicketComments  => 'Staff',
+    ShowOutgoingEmail   => 'Staff',
+    Watch               => 'General',
+    WatchAsAdminCc      => 'Staff',
+    CreateTicket        => 'General',
+    ReplyToTicket       => 'General',
+    CommentOnTicket     => 'General',
+    OwnTicket           => 'Admin',
+    ModifyTicket        => 'Staff',
+    ModifyTicketStatus  => 'Staff',
+    DeleteTicket        => 'Staff',
+    RejectTicket        => 'Staff',
+    TakeTicket          => 'Staff',
+    StealTicket         => 'Staff',
+    ForwardMessage      => 'Staff',
+};
+
 # Tell RT::ACE that this sort of object can get acls granted
 $RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
 
@@ -186,6 +217,18 @@ sub AvailableRights {
     return($RIGHTS);
 }
 
+=head2 RightCategories
+
+Returns a hashref where the keys are rights for this type of object and the
+values are the category (General, Staff, Admin) the right falls into.
+
+=cut
+
+sub RightCategories {
+    return $RIGHT_CATEGORIES;
+}
+
+
 # {{{ ActiveStatusArray
 
 sub lifecycle {
diff --git a/lib/RT/System.pm b/lib/RT/System.pm
index b5ee5c6..9280645 100755
--- a/lib/RT/System.pm
+++ b/lib/RT/System.pm
@@ -93,6 +93,21 @@ our $RIGHTS = {
     ExecuteCode => "allow writing Perl code in templates, scrips, etc", # loc_pair
 };
 
+our $RIGHT_CATEGORIES = {
+    SuperUser              => 'Admin',
+    AdminAllPersonalGroups => 'Admin',
+    AdminOwnPersonalGroups => 'Admin',
+    AdminUsers             => 'Admin',
+    ModifySelf             => 'Staff',
+    DelegateRights         => 'Admin',
+    ShowConfigTab          => 'Admin',
+    ShowApprovalsTab       => 'Admin',
+    ShowGlobalTemplates    => 'Staff',
+    LoadSavedSearch        => 'General',
+    CreateSavedSearch      => 'General',
+    ExecuteCode            => 'Admin',
+};
+
 # Tell RT::ACE that this sort of object can get acls granted
 $RT::ACE::OBJECT_TYPES{'RT::System'} = 1;
 
@@ -131,6 +146,30 @@ sub AvailableRights {
     return(\%rights);
 }
 
+=head2 RightCategories
+
+Returns a hashref where the keys are rights for this type of object and the
+values are the category (General, Staff, Admin) the right falls into.
+
+=cut
+
+sub RightCategories {
+    my $self = shift;
+
+    my $queue = RT::Queue->new($RT::SystemUser);
+    my $group = RT::Group->new($RT::SystemUser);
+    my $cf    = RT::CustomField->new($RT::SystemUser);
+
+    my $qr = $queue->RightCategories();
+    my $gr = $group->RightCategories();
+    my $cr = $cf->RightCategories();
+
+    # Build a merged list of all system wide rights, queue rights and group rights.
+    my %rights = (%{$RIGHT_CATEGORIES}, %{$gr}, %{$qr}, %{$cr});
+
+    return(\%rights);
+}
+
 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
 
 Adds the given rights to the list of possible rights.  This method
@@ -146,6 +185,19 @@ sub AddRights {
                                       map { lc($_) => $_ } keys %new);
 }
 
+=head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
+
+Adds the given right and category pairs to the list of right categories.  This
+method should be called during server startup, not at runtime.
+
+=cut
+
+sub AddRightCategories {
+    my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
+    my %new = @_;
+    $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
+}
+
 sub _Init {
     my $self = shift;
     $self->SUPER::_Init (@_) if @_ && $_[0];
diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
index 0878f3b..85feff3 100644
--- a/share/html/Admin/Elements/EditRights
+++ b/share/html/Admin/Elements/EditRights
@@ -25,6 +25,7 @@ unless ( $AddPrincipal ) {
 <script type="text/javascript">
   jQuery(function() {
       jQuery(".rights-editor").tabs();
+      jQuery(".rights-editor .category-tabs").tabs();
   });
 </script>
 
@@ -66,15 +67,31 @@ for my $category (@$Principals) {
   </ul>
 
 <%perl>
-# Find all our available rights
-my %available_rights;
+# Find all our available rights...
+my (%available_rights, %categories);
 if ( blessed($Context) and $Context->can('AvailableRights') ) { 
     %available_rights = %{$Context->AvailableRights};
-}
-else {
+} else {
     %available_rights = ( loc('System Error') => loc("No rights found") );
 }
 
+# ...and their categories
+if ( blessed($Context) and $Context->can('RightCategories') ) { 
+    my %right_categories = %{$Context->RightCategories};
+    
+    for my $right (keys %available_rights) {
+        push @{$categories{$right_categories{$right}}}, $right;
+    }
+}
+
+my %category_desc = (
+    'General' => 'General rights',
+    'Staff'   => 'Rights for Staff',
+    'Admin'   => 'Rights for Administrators',
+);
+
+my %catsort = ( General => 1, Staff => 2, Admin => 3, );
+
 # Find all the current rights
 my %current_rights;
 for my $collection (map { $_->[1] } @$Principals) {
@@ -101,9 +118,17 @@ for my $category (@$Principals) {
 </%perl>
 
   <div id="<% $id %>">
-    <h3><&|/l&>Rights for</&> <% $loc ? loc($display) : $display %></h3>
+    <h3><% $loc ? loc($display) : $display %></h3>
+    <div class="category-tabs">
+      <ul>
+% for my $category (sort { $catsort{$a} <=> $catsort{$b} } keys %categories) {
+        <li><a href="#<% "$id-$category" %>"><% loc($category_desc{$category} || 'Miscellaneous') %></a></li>
+% }
+      </ul>
+% for my $category (sort { $catsort{$a} <=> $catsort{$b} } keys %categories) {
+    <div id="<% "$id-$category" %>">
     <ul class="rights-list">
-% for my $right (sort keys %available_rights) {
+%     for my $right (sort @{$categories{$category}}) {
       <li>
         <input type="checkbox" class="checkbox"
                name="SetRights-<% $acldesc %>"
@@ -114,8 +139,11 @@ for my $category (@$Principals) {
           <% loc($available_rights{$right}) %>
         </label>
       </li>
-% }
+%     }
     </ul>
+    </div>
+% }
+    </div>
     <input type="hidden" name="CheckACL" value="<% $acldesc %>" />
   </div>
 <%perl>
@@ -127,8 +155,16 @@ if ( $AddPrincipal ) {
 </%perl>
   <div id="acl-addprincipal">
     <h3><&|/l&>Add rights for this</&> <% loc($AddPrincipal) %></h3>
+    <div class="category-tabs">
+      <ul>
+% for my $category (sort { $catsort{$a} <=> $catsort{$b} } keys %categories) {
+        <li><a href="#acl-addprincipal-<% $category %>"><% loc($category_desc{$category} || 'Miscellaneous') %></a></li>
+% }
+      </ul>
+% for my $category (sort { $catsort{$a} <=> $catsort{$b} } keys %categories) {
+    <div id="acl-addprincipal-<% $category %>">
     <ul class="rights-list">
-% for my $right (sort keys %available_rights) {
+%     for my $right (sort @{$categories{$category}}) {
       <li>
         <input type="checkbox" class="checkbox"
                name="SetRights-<% $acldesc %>"
@@ -138,8 +174,11 @@ if ( $AddPrincipal ) {
           <% loc($available_rights{$right}) %>
         </label>
       </li>
-% }
+%     }
     </ul>
+    </div>
+% }
+  </div>
   </div>
 % }
 </div>
diff --git a/share/html/NoAuth/css/base/rights-editor.css b/share/html/NoAuth/css/base/rights-editor.css
index 82d7a56..5b5c216 100644
--- a/share/html/NoAuth/css/base/rights-editor.css
+++ b/share/html/NoAuth/css/base/rights-editor.css
@@ -7,6 +7,7 @@
 .rights-editor {
     border: none;
     background: transparent;
+    width: 100%;
 }
 
 /* Position and style the left tabs */
@@ -15,6 +16,7 @@
     background: transparent;
     border: none;
     color: black;
+    width: 25%;
 }
 
 .rights-editor > .ui-tabs-nav li {
@@ -43,7 +45,8 @@ li.category ~ li.category {
 /* Position the outer-most panel */
 .rights-editor > .ui-tabs-panel {
     position: static;
-    float: right;
+    float: left;
+    width: 72%;
 }
 
 .rights-editor .ui-tabs-panel {
@@ -57,3 +60,7 @@ li.category ~ li.category {
 .rights-editor ul.rights-list {
     list-style: none;
 }
+
+.category-tabs {
+    width: 100%;
+}

commit ce648f7a2f9734c6095c266514dceddfcc7e8bde
Merge: ec2817c 5c68ecc
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Sep 2 17:24:44 2010 -0400

    Merge branch '3.9-trunk' into rightsmatrix


commit 25f5dc159c508d4e089b96196a2ac4fab461349f
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 10:03:30 2010 -0400

    ucfirst a few all lowercase RT::System right summaries

diff --git a/lib/RT/System.pm b/lib/RT/System.pm
index 9280645..972eb42 100755
--- a/lib/RT/System.pm
+++ b/lib/RT/System.pm
@@ -85,12 +85,12 @@ our $RIGHTS = {
     ModifySelf     => "Modify one's own RT account",                  # loc_pair
     DelegateRights =>
       "Delegate specific rights which have been granted to you.",     # loc_pair
-    ShowConfigTab => "show Configuration tab",     # loc_pair
-    ShowApprovalsTab => "show Approvals tab",     # loc_pair
-    ShowGlobalTemplates => "show global templates",     # loc_pair
-    LoadSavedSearch => "allow loading of saved searches",     # loc_pair
-    CreateSavedSearch => "allow creation of saved searches",      # loc_pair
-    ExecuteCode => "allow writing Perl code in templates, scrips, etc", # loc_pair
+    ShowConfigTab => "Show Configuration tab",     # loc_pair
+    ShowApprovalsTab => "Show Approvals tab",     # loc_pair
+    ShowGlobalTemplates => "Show global templates",     # loc_pair
+    LoadSavedSearch => "Allow loading of saved searches",     # loc_pair
+    CreateSavedSearch => "Allow creation of saved searches",      # loc_pair
+    ExecuteCode => "Allow writing Perl code in templates, scrips, etc", # loc_pair
 };
 
 our $RIGHT_CATEGORIES = {

commit f4bb682254078968216d63ee1766f3b2fb65781e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 11:04:51 2010 -0400

    Fix our minimal rights editor testing for the new UI

diff --git a/t/web/rights.t b/t/web/rights.t
index 7b93a7e..3fa3829 100644
--- a/t/web/rights.t
+++ b/t/web/rights.t
@@ -17,8 +17,8 @@ sub get_rights {
     my $principal_id = shift;
     my $object = shift;
     $agent->form_number(3);
-    my @inputs = $agent->current_form->find_input("RevokeRight-$principal_id-$object");
-    my @rights = sort grep $_, map $_->possible_values, grep $_, @inputs;
+    my @inputs = $agent->current_form->find_input("SetRights-$principal_id-$object");
+    my @rights = sort grep $_, map $_->possible_values, grep $_ && $_->value, @inputs;
     return @rights;
 };
 
@@ -34,7 +34,7 @@ diag "revoke all global rights from Everyone group";
 my @has = get_rights( $m, $everyone_gid, 'RT::System-1' );
 if ( @has ) {
     $m->form_number(3);
-    $m->tick("RevokeRight-$everyone_gid-RT::System-1", $_) foreach @has;
+    $m->untick("SetRights-$everyone_gid-RT::System-1", $_) foreach @has;
     $m->submit;
     
     is_deeply([get_rights( $m, $everyone_gid, 'RT::System-1' )], [], 'deleted all rights' );
@@ -45,7 +45,7 @@ if ( @has ) {
 diag "grant SuperUser right to everyone";
 {
     $m->form_number(3);
-    $m->select("GrantRight-$everyone_gid-RT::System-1", ['SuperUser']);
+    $m->tick("SetRights-$everyone_gid-RT::System-1", 'SuperUser');
     $m->submit;
 
     $m->content_contains('Right Granted', 'got message');
@@ -57,7 +57,7 @@ diag "grant SuperUser right to everyone";
 diag "revoke the right";
 {
     $m->form_number(3);
-    $m->tick("RevokeRight-$everyone_gid-RT::System-1", 'SuperUser');
+    $m->untick("SetRights-$everyone_gid-RT::System-1", 'SuperUser');
     $m->submit;
 
     $m->content_contains('Right revoked', 'got message');
@@ -70,7 +70,7 @@ diag "revoke the right";
 diag "return rights the group had in the beginning";
 if ( @has ) {
     $m->form_number(3);
-    $m->select("GrantRight-$everyone_gid-RT::System-1", \@has);
+    $m->tick("SetRights-$everyone_gid-RT::System-1", $_) for @has;
     $m->submit;
 
     $m->content_contains('Right Granted', 'got message');

commit 92caed09ad97d68dd590db23055c4d6652e6884e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 12:02:27 2010 -0400

    Show implicit subgroups also getting rights for user defined groups

diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
index 85feff3..e108af3 100644
--- a/share/html/Admin/Elements/EditRights
+++ b/share/html/Admin/Elements/EditRights
@@ -118,7 +118,23 @@ for my $category (@$Principals) {
 </%perl>
 
   <div id="<% $id %>">
-    <h3><% $loc ? loc($display) : $display %></h3>
+    <h3>
+      <% $loc ? loc($display) : $display %>
+<%perl>
+if ($obj->isa('RT::Group') and $obj->Domain eq 'UserDefined') {
+    my $subgroups = $obj->GroupMembersObj( Recursively => 1 );
+    $subgroups->LimitToUserDefinedGroups;
+    $subgroups->Limit( FIELD => 'Name', OPERATOR => '!=', VALUE => $obj->Name );
+
+    if ( $subgroups->Count ) {
+        my $inc = join ", ", map $_->Name, @{$subgroups->ItemsArrayRef};
+</%perl>
+      <span class="subgroups"><&|/l, $inc &>includes [_1]</&></span>\
+<%perl>
+    }
+}
+</%perl>
+    </h3>
     <div class="category-tabs">
       <ul>
 % for my $category (sort { $catsort{$a} <=> $catsort{$b} } keys %categories) {
diff --git a/share/html/NoAuth/css/base/rights-editor.css b/share/html/NoAuth/css/base/rights-editor.css
index 5b5c216..937123c 100644
--- a/share/html/NoAuth/css/base/rights-editor.css
+++ b/share/html/NoAuth/css/base/rights-editor.css
@@ -54,7 +54,14 @@ li.category ~ li.category {
 }
 
 .rights-editor .ui-tabs-panel h3 {
+    color: black;
     margin-top: 0;
+    line-height: 0.8em;
+}
+
+.rights-editor .ui-tabs-panel h3 .subgroups {
+    color: #444;
+    font-size: 80%;
 }
 
 .rights-editor ul.rights-list {

commit 71c539e174405efd7b19fa5db62dc9e4ae058bbf
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 12:53:18 2010 -0400

    Add a callback to let extensions get at principals and context

diff --git a/share/html/Admin/Elements/EditRights b/share/html/Admin/Elements/EditRights
index e108af3..55c2bb0 100644
--- a/share/html/Admin/Elements/EditRights
+++ b/share/html/Admin/Elements/EditRights
@@ -6,6 +6,9 @@ $AddPrincipal => undef
 <%init>
 use Scalar::Util qw(blessed);
 
+# Let callbacks get at principals and context before we do anything with them
+$m->callback( Principals => $Principals, Context => $Context );
+
 # Try to detect if we want to include an add user/group box
 unless ( $AddPrincipal ) {
     my $last = $Principals->[-1];

commit 249edb554ec49326ebbfb4b5ad57cf5aac3e23cc
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 13:06:13 2010 -0400

    Float over the extra buttons so we don't break up onto two lines

diff --git a/share/html/NoAuth/css/web2/forms.css b/share/html/NoAuth/css/web2/forms.css
index 4c33f1f..609f65c 100755
--- a/share/html/NoAuth/css/web2/forms.css
+++ b/share/html/NoAuth/css/web2/forms.css
@@ -167,6 +167,7 @@ div.submit {
 
 div.submit .extra-buttons {
  text-align: left;
+ float: left;
 }
 
 

commit 3628036d29ebead5505f9059af4508a14b425bd5
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 13:13:58 2010 -0400

    Add the AddRightCategories method from RT::System to appropriate packages

diff --git a/lib/RT/CustomField_Overlay.pm b/lib/RT/CustomField_Overlay.pm
index 2cceb76..da50a41 100755
--- a/lib/RT/CustomField_Overlay.pm
+++ b/lib/RT/CustomField_Overlay.pm
@@ -181,6 +181,7 @@ our $RIGHT_CATEGORIES = {
 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
 
 __PACKAGE__->AddRights(%$RIGHTS);
+__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
 
 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
 
@@ -213,6 +214,19 @@ sub RightCategories {
     return $RIGHT_CATEGORIES;
 }
 
+=head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
+
+Adds the given right and category pairs to the list of right categories.  This
+method should be called during server startup, not at runtime.
+
+=cut
+
+sub AddRightCategories {
+    my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
+    my %new = @_;
+    $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
+}
+
 =head1 NAME
 
   RT::CustomField_Overlay - overlay for RT::CustomField
diff --git a/lib/RT/Group_Overlay.pm b/lib/RT/Group_Overlay.pm
index 603fd35..dc13082 100755
--- a/lib/RT/Group_Overlay.pm
+++ b/lib/RT/Group_Overlay.pm
@@ -124,6 +124,7 @@ $RT::ACE::OBJECT_TYPES{'RT::Group'} = 1;
 # stuff the rights into a hash of rights that can exist.
 
 __PACKAGE__->AddRights(%$RIGHTS);
+__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
 
 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
 
@@ -162,6 +163,19 @@ sub RightCategories {
     return $RIGHT_CATEGORIES;
 }
 
+=head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
+
+Adds the given right and category pairs to the list of right categories.  This
+method should be called during server startup, not at runtime.
+
+=cut
+
+sub AddRightCategories {
+    my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
+    my %new = @_;
+    $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
+}
+
 
 # {{{ sub SelfDescription
 
diff --git a/lib/RT/Queue_Overlay.pm b/lib/RT/Queue_Overlay.pm
index cf655d0..77535a2 100755
--- a/lib/RT/Queue_Overlay.pm
+++ b/lib/RT/Queue_Overlay.pm
@@ -157,6 +157,7 @@ $RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
 # stuff the rights into a hash of rights that can exist.
 
 __PACKAGE__->AddRights(%$RIGHTS);
+__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
 
 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
 
@@ -173,6 +174,19 @@ sub AddRights {
                                       map { lc($_) => $_ } keys %new);
 }
 
+=head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
+
+Adds the given right and category pairs to the list of right categories.  This
+method should be called during server startup, not at runtime.
+
+=cut
+
+sub AddRightCategories {
+    my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
+    my %new = @_;
+    $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
+}
+
 sub AddLink {
     my $self = shift;
     my %args = ( Target => '',
diff --git a/lib/RT/System.pm b/lib/RT/System.pm
index 972eb42..8bee2d8 100755
--- a/lib/RT/System.pm
+++ b/lib/RT/System.pm
@@ -112,6 +112,7 @@ our $RIGHT_CATEGORIES = {
 $RT::ACE::OBJECT_TYPES{'RT::System'} = 1;
 
 __PACKAGE__->AddRights(%$RIGHTS);
+__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
 
 =head2 AvailableRights
 

commit c09723d936967f6929c80f32206d5c93ce312910
Merge: 3628036 7f41a58
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 13:26:23 2010 -0400

    Merge branch '3.9-trunk' into rightsmatrix


commit beda996dd9748b1f6ced33578cd72e7057b1b3c5
Merge: c09723d b789a45
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Sep 3 14:47:47 2010 -0400

    Merge branch '3.9-trunk' into rightsmatrix


-----------------------------------------------------------------------


More information about the Rt-commit mailing list