[Rt-commit] rt branch, master, updated. rt-4.1.8-211-g4ee6d1a
Alex Vandiver
alexmv at bestpractical.com
Thu May 2 15:56:22 EDT 2013
The branch, master has been updated
via 4ee6d1a70d67d213bc51f0813f9e244d65532038 (commit)
via da1a67bfea314349a5d0a5bf7deb2bb446cfad4f (commit)
via 7e227664346b4b27b4622a5ea7a293b684cb6296 (commit)
via a66d41f692e83b026a15d464cdf5e308c0129b49 (commit)
via 794d3cc615035714795a1bf92bd955f0b278a7c7 (commit)
via 38eab60e5115186a82fbec57ffdefb54d8395681 (commit)
via 871f4f61f0926942871b13fcb6ccc80257a2c733 (commit)
via c72a5f5261b40a4a3c46dc41c04cf09b4a8ca224 (commit)
via eff3e1fc9b9dce47bc5136a74931449d6de81a2d (commit)
via c0b2b29e4693ed6d04fa21d07ba49187de0404c8 (commit)
via 00e34fe5b5842157558306ad6bcad492367f684a (commit)
via 906cd2b7b0c1aea2eccc709172ed523d98b7ecbe (commit)
via ebae01c6cdd5f2482cbf4ca282d96ab793a24007 (commit)
via 9ebdbe566b8a298f056fa42e936ffbbc77e59a07 (commit)
via 8915771b39bae9e6479123a81ec5f830c4d73b2e (commit)
via 54f1a73b4fc057071d66ddd339d940c3d36cc826 (commit)
via 2ef7f4839001155dbf2806053da0fb61eca776a2 (commit)
via 7834bfa81b0f16867f440318f38f92a53dbf920e (commit)
via 0cfd6223b87c9e6b1997482f2a0b78b2d9304f90 (commit)
via adc47505d64b8afcf4127b7a03dacba9b42f7b8b (commit)
via 42e4fddb5f8b787eab1e52624781238ff7b852d9 (commit)
via 479999c26f7dedd98c23f50288d7807373a83b93 (commit)
via 72449cac0140de4ac370ccfb0565b4e7a74a8965 (commit)
via 808beaeaf0a531fb5f080164b25697538b9b1dad (commit)
via 15a18f9fc0c9960ef580905378c38e83bd962db8 (commit)
via dc221d91198594f515ab22937f3bb6b421d2faf0 (commit)
via d731ee1ee9696c7f42dfe5ce581eb776379fe477 (commit)
via a2263b7b789455b92eab1791a06c58e8bc43df08 (commit)
via c9af1f4084675d9b2fe6cc76deb6278dfb2f18d3 (commit)
via ac8392aae70f674417e89a0090abbfec25ab26ab (commit)
via 28991d859ee23734e5c61406d2baad37c4f6f327 (commit)
via 19b60e7723e8a8145649479cba6b68a9a33bfd5e (commit)
via d4fee32ea48d8b2999329d2505107fa6dd58c8a0 (commit)
via c8e5badb67af85d94565028faef82b1d4946c1ed (commit)
via 8d40c6ff11ff17ecf047c65c1f45133ed7ebb5c3 (commit)
via ae0945c0b383d8cfb693f03b01e22575849f460d (commit)
via d3795e7c37531fdd56b46d4322522989f4a3c737 (commit)
via a512f4106fbf32cd1fd6f4e915156b68aa4ec75c (commit)
via 9e8748ccb46aecda82be408734bc369482f30dcc (commit)
via cef9cc19efc5b89725d6fa6689637536e31877b5 (commit)
via 9c2e33551f1b83e216e2c4500022bd0ac1c61f95 (commit)
via a28cf0cfc0f739b61b8dbc99339e5af1cc2f9903 (commit)
via 946dc6e1708e6bcaf61a1c0bfe0a75e59a209764 (commit)
via 3823e4c87f268901ecc1b6c5be53a114c6fddb3b (commit)
via d6a86655aa8b5f4d5d39702fdaaa24241c53871c (commit)
via 5cee2fe87cd00c09fd92bc8f803334933b161225 (commit)
via 35b87bb8d3f96a4c3090b8da1cae23f19bc49f81 (commit)
via 042ac27969a8d38038aee9db16cb96c2b79eb5d2 (commit)
via a356872c01c7a8a684eac81208482ae1717381d3 (commit)
via efaf506bfa077ee07ac0cfa4b4e3bc8cad278bbf (commit)
via 529800c2c90f0c6cc76b8a73416c1874d1d925cb (commit)
via 27bd738eafa5367e6013836210e8a4a7d4244d71 (commit)
via 5453b8d458aaab66dc8ac4fd16509137a7349de4 (commit)
via c2120ccbecf734475fc3a142554a6636fdcd6e5f (commit)
via d28f76f9be059c21907419d7364c395f326c056d (commit)
via dddc05435f84eed91a2a452d594432cb9412e42c (commit)
via 5d153f131eb1943de0b2b4904aadcb9d1c306808 (commit)
via 61662a68e9bca84ef74c7a728368d8d35928e910 (commit)
via e483b9d112afca6faa007dd100aa4908926c2f5a (commit)
from cf92245cec4fa440a1e196b8a145e391cc3e9ec3 (commit)
Summary of changes:
.gitattributes | 2 +-
Makefile.in | 18 +-
docs/UPGRADING-3.4 | 7 +
docs/UPGRADING-4.0 | 12 +
etc/RT_Config.pm.in | 12 +
etc/upgrade/3.9.8/schema.Oracle | 0
etc/upgrade/3.9.8/schema.Pg | 0
etc/upgrade/3.9.8/schema.SQLite | 0
etc/upgrade/3.9.8/schema.mysql | 0
etc/upgrade/4.0.1/acl.Pg | 0
etc/upgrade/4.0.12/schema.Oracle | 1 +
etc/upgrade/4.0.12/schema.Pg | 1 +
etc/upgrade/4.0.12/schema.mysql | 1 +
etc/upgrade/4.1.1/acl.Pg | 0
lib/RT.pm | 46 ++-
lib/RT/Action/CreateTickets.pm | 4 +-
lib/RT/Class.pm | 2 +-
lib/RT/Dashboard.pm | 2 +-
lib/RT/Dashboard/Mailer.pm | 8 +-
lib/RT/Handle.pm | 6 +
lib/RT/Interface/Email.pm | 8 +-
lib/RT/Interface/Web.pm | 38 +-
lib/RT/Interface/Web/Session.pm | 4 +-
lib/RT/SavedSearch.pm | 2 +-
lib/RT/SearchBuilder/Role/Roles.pm | 18 +-
lib/RT/SharedSetting.pm | 4 +-
lib/RT/Test.pm | 3 +
lib/RT/Ticket.pm | 12 +
lib/RT/Tickets.pm | 97 ++++-
lib/RT/User.pm | 2 +-
sbin/rt-setup-database.in | 10 +-
.../ScrubHTML => Helpers/Autocomplete/autohandler} | 10 +-
.../{Elements/ScrubHTML => Helpers/autohandler} | 9 +-
share/html/NoAuth/css/aileron/nav.css | 2 +-
share/html/Prefs/Quicksearch.html | 10 +-
share/html/REST/1.0/Forms/attachment/default | 4 +-
share/html/SelfService/Prefs.html | 42 +++
share/po/ar.po | 393 ++++++++++++---------
share/po/bg.po | 393 ++++++++++++---------
share/po/ca.po | 393 ++++++++++++---------
share/po/cs.po | 393 ++++++++++++---------
share/po/da.po | 393 ++++++++++++---------
share/po/de.po | 393 ++++++++++++---------
share/po/el.po | 393 ++++++++++++---------
share/po/en_GB.po | 290 +++++++--------
share/po/es.po | 393 ++++++++++++---------
share/po/et.po | 393 ++++++++++++---------
share/po/fi.po | 393 ++++++++++++---------
share/po/fr.po | 393 ++++++++++++---------
share/po/he.po | 393 ++++++++++++---------
share/po/hr.po | 393 ++++++++++++---------
share/po/hu.po | 393 ++++++++++++---------
share/po/id.po | 393 ++++++++++++---------
share/po/is.po | 393 ++++++++++++---------
share/po/it.po | 393 ++++++++++++---------
share/po/ja.po | 393 ++++++++++++---------
share/po/lt.po | 393 ++++++++++++---------
share/po/lv.po | 393 ++++++++++++---------
share/po/mk.po | 393 ++++++++++++---------
share/po/nb.po | 393 ++++++++++++---------
share/po/nl.po | 393 ++++++++++++---------
share/po/nn.po | 393 ++++++++++++---------
share/po/oc.po | 393 ++++++++++++---------
share/po/pl.po | 393 ++++++++++++---------
share/po/pt.po | 393 ++++++++++++---------
share/po/pt_BR.po | 393 ++++++++++++---------
share/po/pt_PT.po | 393 ++++++++++++---------
share/po/rt.pot | 393 ++++++++++++---------
share/po/ru.po | 393 ++++++++++++---------
share/po/sk.po | 393 ++++++++++++---------
share/po/sl.po | 393 ++++++++++++---------
share/po/sv.po | 393 ++++++++++++---------
share/po/tr.po | 393 ++++++++++++---------
share/po/zh_CN.po | 391 +++++++++++---------
share/po/zh_TW.po | 391 +++++++++++---------
t/99-policy.t | 5 +-
t/api/ticket.t | 28 ++
t/articles/class.t | 16 +-
t/ticket/search_by_watcher.t | 73 ++--
t/web/helpers-http-cache-headers.t | 97 +++++
80 files changed, 8765 insertions(+), 6678 deletions(-)
mode change 100755 => 100644 etc/upgrade/3.9.8/schema.Oracle
mode change 100755 => 100644 etc/upgrade/3.9.8/schema.Pg
mode change 100755 => 100644 etc/upgrade/3.9.8/schema.SQLite
mode change 100755 => 100644 etc/upgrade/3.9.8/schema.mysql
mode change 100755 => 100644 etc/upgrade/4.0.1/acl.Pg
create mode 100644 etc/upgrade/4.0.12/schema.Oracle
create mode 100644 etc/upgrade/4.0.12/schema.Pg
create mode 100644 etc/upgrade/4.0.12/schema.mysql
mode change 100755 => 100644 etc/upgrade/4.1.1/acl.Pg
copy share/html/{Elements/ScrubHTML => Helpers/Autocomplete/autohandler} (92%)
copy share/html/{Elements/ScrubHTML => Helpers/autohandler} (92%)
create mode 100644 t/web/helpers-http-cache-headers.t
- Log -----------------------------------------------------------------
commit 4ee6d1a70d67d213bc51f0813f9e244d65532038
Merge: cf92245 da1a67b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date: Thu May 2 15:24:33 2013 -0400
Merge branch '4.0-trunk'
This merges work on requestor bundling (merged to 4.0-trunk in 7e22766),
which is made more complicated by three factors:
* The old (unused) bundling code was removed in 4.2, in 093efcc
* Tickets_SQL.pm was merged into Tickets.pm in c6be454
* Watcher searching was refactored into a SearchBuilder role in 4b15643
This merge moves the newly added bundling code into the appropriate
places in lib/RT/Tickets.pm and lib/RT/SearchBuilder/Role/Roles.pm
Conflicts:
lib/RT/Tickets.pm
lib/RT/Tickets_SQL.pm
share/html/NoAuth/RichText/ckeditor/contents.css
share/html/Prefs/Quicksearch.html
share/html/Ticket/Elements/ShowRequestor
t/99-policy.t
t/api/ticket.t
diff --cc etc/upgrade/4.1.1/acl.Pg
index 9e8fc0a,0000000..9e8fc0a
mode 100755,000000..100644
--- a/etc/upgrade/4.1.1/acl.Pg
+++ b/etc/upgrade/4.1.1/acl.Pg
diff --cc lib/RT/SearchBuilder/Role/Roles.pm
index 8031b82,0000000..7167b13
mode 100644,000000..100644
--- a/lib/RT/SearchBuilder/Role/Roles.pm
+++ b/lib/RT/SearchBuilder/Role/Roles.pm
@@@ -1,372 -1,0 +1,378 @@@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
+# <sales 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 }}}
+
+use strict;
+use warnings;
+
+package RT::SearchBuilder::Role::Roles;
+use Role::Basic;
+use Scalar::Util qw(blessed);
+
+=head1 NAME
+
+RT::Record::Role::Roles - Common methods for records which "watchers" or "roles"
+
+=head1 REQUIRES
+
+=head2 L<RT::SearchBuilder::Role>
+
+=cut
+
+with 'RT::SearchBuilder::Role';
+
+require RT::System;
+require RT::Principal;
+require RT::Group;
+require RT::User;
+
+require RT::EmailParser;
+
+=head1 PROVIDES
+
+=head2 _RoleGroupClass
+
+Returns the class name on which role searches should be based. This relates to
+the internal L<RT::Group/Domain> and distinguishes between roles on the objects
+being searched and their counterpart roles on containing classes. For example,
+limiting on L<RT::Queue> roles while searching for L<RT::Ticket>s.
+
+The default implementation is:
+
+ blessed($self->NewItem)
+
+which is the class that this collection object searches and instatiates objects
+for. If you're doing something hinky, you may need to override this method.
+
+=cut
+
+sub _RoleGroupClass {
+ my $self = shift;
+ return blessed($self->NewItem);
+}
+
+sub _RoleGroupsJoin {
+ my $self = shift;
+ my %args = (New => 0, Class => '', Type => '', @_);
+
+ $args{'Class'} ||= $self->_RoleGroupClass;
+
+ return $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $args{'Type'} }
+ if $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $args{'Type'} }
+ && !$args{'New'};
+
+ # If we're looking at a role group on a class that "contains" this record
+ # (i.e. roles on queues for tickets), then we assume that the current
+ # record has a column named after the containing class (i.e.
+ # Tickets.Queue).
+ my $instance = $self->_RoleGroupClass eq $args{Class} ? "id" : $args{Class};
+ $instance =~ s/^RT:://;
+
+ # Watcher groups are always created for each record, so we use INNER join.
+ my $groups = $self->Join(
+ ALIAS1 => 'main',
+ FIELD1 => $instance,
+ TABLE2 => 'Groups',
+ FIELD2 => 'Instance',
+ ENTRYAGGREGATOR => 'AND',
+ );
+ $self->Limit(
+ LEFTJOIN => $groups,
+ ALIAS => $groups,
+ FIELD => 'Domain',
+ VALUE => $args{'Class'} .'-Role',
+ );
+ $self->Limit(
+ LEFTJOIN => $groups,
+ ALIAS => $groups,
+ FIELD => 'Type',
+ VALUE => $args{'Type'},
+ ) if $args{'Type'};
+
+ $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $args{'Type'} } = $groups
+ unless $args{'New'};
+
+ return $groups;
+}
+
+sub _GroupMembersJoin {
+ my $self = shift;
+ my %args = (New => 1, GroupsAlias => undef, Left => 1, @_);
+
+ return $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} }
+ if $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} }
+ && !$args{'New'};
+
+ my $alias = $self->Join(
+ $args{'Left'} ? (TYPE => 'LEFT') : (),
+ ALIAS1 => $args{'GroupsAlias'},
+ FIELD1 => 'id',
+ TABLE2 => 'CachedGroupMembers',
+ FIELD2 => 'GroupId',
+ ENTRYAGGREGATOR => 'AND',
+ );
+ $self->Limit(
+ LEFTJOIN => $alias,
+ ALIAS => $alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
+
+ $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } = $alias
+ unless $args{'New'};
+
+ return $alias;
+}
+
+=head2 _WatcherJoin
+
+Helper function which provides joins to a watchers table both for limits
+and for ordering.
+
+=cut
+
+sub _WatcherJoin {
+ my $self = shift;
+
+ my $groups = $self->_RoleGroupsJoin(@_);
+ my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
+ # XXX: work around, we must hide groups that
+ # are members of the role group we search in,
+ # otherwise them result in wrong NULLs in Users
+ # table and break ordering. Now, we know that
+ # RT doesn't allow to add groups as members of the
+ # ticket roles, so we just hide entries in CGM table
+ # with MemberId == GroupId from results
+ $self->Limit(
+ LEFTJOIN => $group_members,
+ FIELD => 'GroupId',
+ OPERATOR => '!=',
+ VALUE => "$group_members.MemberId",
+ QUOTEVALUE => 0,
+ );
+ my $users = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $group_members,
+ FIELD1 => 'MemberId',
+ TABLE2 => 'Users',
+ FIELD2 => 'id',
+ );
+ return ($groups, $group_members, $users);
+}
+
+
+sub RoleLimit {
+ my $self = shift;
+ my %args = (
+ TYPE => '',
+ CLASS => '',
+ FIELD => undef,
+ OPERATOR => '=',
+ VALUE => undef,
+ @_
+ );
+
+ my $class = $args{CLASS} || $self->_RoleGroupClass;
+
+ $args{FIELD} ||= 'id' if $args{VALUE} =~ /^\d+$/;
+
+ my $type = delete $args{TYPE};
+ if ($type and not $class->HasRole($type)) {
+ RT->Logger->warn("RoleLimit called with invalid role $type for $class");
+ return;
+ }
+
+ my $column = $type ? $class->Role($type)->{Column} : undef;
+
+ # if it's equality op and search by Email or Name then we can preload user
+ # we do it to help some DBs better estimate number of rows and get better plans
+ if ( $args{OPERATOR} =~ /^!?=$/
+ && (!$args{FIELD} || $args{FIELD} eq 'Name' || $args{FIELD} eq 'EmailAddress') ) {
+ my $o = RT::User->new( $self->CurrentUser );
+ my $method =
+ !$args{FIELD}
+ ? ($column ? 'Load' : 'LoadByEmail')
+ : $args{FIELD} eq 'EmailAddress' ? 'LoadByEmail': 'Load';
+ $o->$method( $args{VALUE} );
+ $args{FIELD} = 'id';
+ $args{VALUE} = $o->id || 0;
+ }
+
+ if ( $column and $args{FIELD} and $args{FIELD} eq 'id' ) {
+ $self->Limit(
+ %args,
+ FIELD => $column,
+ );
+ return;
+ }
+
+ $args{FIELD} ||= 'EmailAddress';
+
- my $groups = $self->_RoleGroupsJoin( Type => $type, Class => $class, New => !$type );
++ my ($groups, $group_members, $users);
++ if ( $args{'BUNDLE'} ) {
++ ($groups, $group_members, $users) = @{ $args{'BUNDLE'} };
++ } else {
++ $groups = $self->_RoleGroupsJoin( Type => $type, Class => $class, New => !$type );
++ }
+
+ $self->_OpenParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
+ if ( $args{OPERATOR} =~ /^IS(?: NOT)?$/i ) {
+ # is [not] empty case
+
- my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
++ $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups );
+ # to avoid joining the table Users into the query, we just join GM
+ # and make sure we don't match records where group is member of itself
+ $self->Limit(
+ LEFTJOIN => $group_members,
+ FIELD => 'GroupId',
+ OPERATOR => '!=',
+ VALUE => "$group_members.MemberId",
+ QUOTEVALUE => 0,
+ );
+ $self->Limit(
+ %args,
+ ALIAS => $group_members,
+ FIELD => 'GroupId',
+ OPERATOR => $args{OPERATOR},
+ VALUE => $args{VALUE},
+ );
+ }
+ elsif ( $args{OPERATOR} =~ /^!=$|^NOT\s+/i ) {
+ # negative condition case
+
+ # reverse op
+ $args{OPERATOR} =~ s/!|NOT\s+//i;
+
+ # XXX: we have no way to build correct "Watcher.X != 'Y'" when condition
+ # "X = 'Y'" matches more then one user so we try to fetch two records and
+ # do the right thing when there is only one exist and semi-working solution
+ # otherwise.
+ my $users_obj = RT::Users->new( $self->CurrentUser );
+ $users_obj->Limit(
+ FIELD => $args{FIELD},
+ OPERATOR => $args{OPERATOR},
+ VALUE => $args{VALUE},
+ );
+ $users_obj->OrderBy;
+ $users_obj->RowsPerPage(2);
+ my @users = @{ $users_obj->ItemsArrayRef };
+
- my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
++ $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups );
+ if ( @users <= 1 ) {
+ my $uid = 0;
+ $uid = $users[0]->id if @users;
+ $self->Limit(
+ LEFTJOIN => $group_members,
+ ALIAS => $group_members,
+ FIELD => 'MemberId',
+ VALUE => $uid,
+ );
+ $self->Limit(
+ %args,
+ ALIAS => $group_members,
+ FIELD => 'id',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ );
+ } else {
+ $self->Limit(
+ LEFTJOIN => $group_members,
+ FIELD => 'GroupId',
+ OPERATOR => '!=',
+ VALUE => "$group_members.MemberId",
+ QUOTEVALUE => 0,
+ );
- my $users = $self->Join(
++ $users ||= $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $group_members,
+ FIELD1 => 'MemberId',
+ TABLE2 => 'Users',
+ FIELD2 => 'id',
+ );
+ $self->Limit(
+ LEFTJOIN => $users,
+ ALIAS => $users,
+ FIELD => $args{FIELD},
+ OPERATOR => $args{OPERATOR},
+ VALUE => $args{VALUE},
+ CASESENSITIVE => 0,
+ );
+ $self->Limit(
+ %args,
+ ALIAS => $users,
+ FIELD => 'id',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ );
+ }
+ } else {
+ # positive condition case
+
- my $group_members = $self->_GroupMembersJoin(
++ $group_members ||= $self->_GroupMembersJoin(
+ GroupsAlias => $groups, New => 1, Left => 0
+ );
- my $users = $self->Join(
++ $users ||= $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $group_members,
+ FIELD1 => 'MemberId',
+ TABLE2 => 'Users',
+ FIELD2 => 'id',
+ );
+ $self->Limit(
+ %args,
+ ALIAS => $users,
+ FIELD => $args{FIELD},
+ OPERATOR => $args{OPERATOR},
+ VALUE => $args{VALUE},
+ CASESENSITIVE => 0,
+ );
+ }
+ $self->_CloseParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
++ return ($groups, $group_members, $users);
+}
+
+1;
diff --cc lib/RT/SharedSetting.pm
index b3b2cc6,3467167..1e704f8
--- a/lib/RT/SharedSetting.pm
+++ b/lib/RT/SharedSetting.pm
@@@ -208,11 -207,10 +208,11 @@@ sub Save
my ($att_id, $att_msg) = $self->SaveAttribute($object, \%args);
if ($att_id) {
- $self->{'Attribute'} = $object->Attributes->WithId($att_id);
+ $self->{'Attribute'} = RT::Attribute->new($self->CurrentUser);
+ $self->{'Attribute'}->Load( $att_id );
$self->{'Id'} = $att_id;
$self->{'Privacy'} = $privacy;
- return ( 1, $self->loc( "Saved [_1] [_2]", $self->ObjectName, $name ) );
+ return ( 1, $self->loc( "Saved [_1] [_2]", $self->loc( $self->ObjectName ), $name ) );
}
else {
$RT::Logger->error($self->ObjectName . " save failure: $att_msg");
diff --cc lib/RT/Ticket.pm
index 653d82e,1945545..46de60e
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@@ -360,10 -382,86 +360,13 @@@ sub Create
$args{'TimeWorked'} = 0 unless defined $args{'TimeWorked'};
$args{'TimeLeft'} = 0 unless defined $args{'TimeLeft'};
- # }}}
-
- # Deal with setting the owner
-
- my $Owner;
- if ( ref( $args{'Owner'} ) eq 'RT::User' ) {
- if ( $args{'Owner'}->id ) {
- $Owner = $args{'Owner'};
- } else {
- $RT::Logger->error('Passed an empty RT::User for owner');
- push @non_fatal_errors,
- $self->loc("Owner could not be set.") . " ".
- $self->loc("Invalid value for [_1]",loc('owner'));
- $Owner = undef;
- }
- }
-
- #If we've been handed something else, try to load the user.
- elsif ( $args{'Owner'} ) {
- $Owner = RT::User->new( $self->CurrentUser );
- $Owner->Load( $args{'Owner'} );
- if (!$Owner->id) {
- $Owner->LoadByEmail( $args{'Owner'} )
- }
- unless ( $Owner->Id ) {
- push @non_fatal_errors,
- $self->loc("Owner could not be set.") . " "
- . $self->loc( "User '[_1]' could not be found.", $args{'Owner'} );
- $Owner = undef;
- }
- }
-
- #If we have a proposed owner and they don't have the right
- #to own a ticket, scream about it and make them not the owner
-
- my $DeferOwner;
- if ( $Owner && $Owner->Id != RT->Nobody->Id
- && !$Owner->HasRight( Object => $QueueObj, Right => 'OwnTicket' ) )
- {
- $DeferOwner = $Owner;
- $Owner = undef;
- $RT::Logger->debug('going to deffer setting owner');
-
- }
-
- #If we haven't been handed a valid owner, make it nobody.
- unless ( defined($Owner) && $Owner->Id ) {
- $Owner = RT::User->new( $self->CurrentUser );
- $Owner->Load( RT->Nobody->Id );
- }
-
- # }}}
-
-# We attempt to load or create each of the people who might have a role for this ticket
-# _outside_ the transaction, so we don't get into ticket creation races
- foreach my $type ( "Cc", "AdminCc", "Requestor" ) {
- $args{ $type } = [ $args{ $type } ] unless ref $args{ $type };
- foreach my $watcher ( splice @{ $args{$type} } ) {
- next unless $watcher;
- if ( $watcher =~ /^\d+$/ ) {
- push @{ $args{$type} }, $watcher;
- } else {
- my @addresses = RT::EmailParser->ParseEmailAddress( $watcher );
- foreach my $address( @addresses ) {
- my $user = RT::User->new( RT->SystemUser );
- my ($uid, $msg) = $user->LoadOrCreateByEmail( $address );
- unless ( $uid ) {
- push @non_fatal_errors,
- $self->loc("Couldn't load or create user: [_1]", $msg);
- } else {
- push @{ $args{$type} }, $user->id;
- }
- }
- }
- }
- }
+ # Figure out users for roles
+ my $roles = {};
+ push @non_fatal_errors, $self->_ResolveRoles( $roles, %args );
+ $args{'Type'} = lc $args{'Type'}
+ if $args{'Type'} =~ /^(ticket|approval|reminder)$/i;
+
$RT::Handle->BeginTransaction();
my %params = (
diff --cc lib/RT/Tickets.pm
index 6437d78,116a4e2..12e4afa
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@@ -367,8 -378,10 +367,10 @@@ sub _EnumLimit
my $o = $class->new( $sb->CurrentUser );
$o->Load($value);
$value = $o->Id || 0;
+ } elsif ( $field eq "Type" ) {
+ $value = lc $value if $value =~ /^(ticket|approval|reminder)$/i;
}
- $sb->_SQLLimit(
+ $sb->Limit(
FIELD => $field,
VALUE => $value,
OPERATOR => $op,
@@@ -3313,124 -3569,7 +3315,213 @@@ BUG: There should be an API for thi
=cut
+=head2 FromSQL
+
+Convert a RT-SQL string into a set of SearchBuilder restrictions.
+
+Returns (1, 'Status message') on success and (0, 'Error Message') on
+failure.
+
+=cut
+
+sub _parser {
+ my ($self,$string) = @_;
+ my $ea = '';
+
++ # Bundling of joins is implemented by dynamically tracking a parallel query
++ # tree in %sub_tree as the TicketSQL is parsed.
++ #
++ # Only positive, OR'd watcher conditions are bundled currently. Each key
++ # in %sub_tree is a watcher type (Requestor, Cc, AdminCc) or the generic
++ # "Watcher" for any watcher type. Owner is not bundled because it is
++ # denormalized into a Tickets column and doesn't need a join. AND'd
++ # conditions are not bundled since a record may have multiple watchers
++ # which independently match the conditions, thus necessitating two joins.
++ #
++ # The values of %sub_tree are arrayrefs made up of:
++ #
++ # * Open parentheses "(" pushed on by the OpenParen callback
++ # * Arrayrefs of bundled join aliases pushed on by the Condition callback
++ # * Entry aggregators (AND/OR) pushed on by the EntryAggregator callback
++ #
++ # The CloseParen callback takes care of backing off the query trees until
++ # outside of the just-closed parenthetical, thus restoring the tree state
++ # an equivalent of before the parenthetical was entered.
++ #
++ # The Condition callback handles starting a new subtree or extending an
++ # existing one, determining if bundling the current condition with any
++ # subtree is possible, and pruning any dangling entry aggregators from
++ # trees.
++ #
++
++ my %sub_tree;
++ my $depth = 0;
++
+ my %callback;
+ $callback{'OpenParen'} = sub {
- $self->_OpenParen
++ $self->_OpenParen;
++ $depth++;
++ push @$_, '(' foreach values %sub_tree;
+ };
+ $callback{'CloseParen'} = sub {
+ $self->_CloseParen;
++ $depth--;
++ foreach my $list ( values %sub_tree ) {
++ if ( $list->[-1] eq '(' ) {
++ pop @$list;
++ pop @$list if $list->[-1] =~ /^(?:AND|OR)$/i;
++ }
++ else {
++ pop @$list while $list->[-2] ne '(';
++ $list->[-1] = pop @$list;
++ }
++ }
++ };
++ $callback{'EntryAggregator'} = sub {
++ $ea = $_[0] || '';
++ push @$_, $ea foreach grep @$_ && $_->[-1] ne '(', values %sub_tree;
+ };
- $callback{'EntryAggregator'} = sub { $ea = $_[0] || '' };
+ $callback{'Condition'} = sub {
+ my ($key, $op, $value) = @_;
+
++ my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
++ my $null_op = ( 'is not' eq lc($op) || 'is' eq lc($op) );
+ # key has dot then it's compound variant and we have subkey
+ my $subkey = '';
+ ($key, $subkey) = ($1, $2) if $key =~ /^([^\.]+)\.(.+)$/;
+
+ # normalize key and get class (type)
+ my $class;
+ if (exists $LOWER_CASE_FIELDS{lc $key}) {
+ $key = $LOWER_CASE_FIELDS{lc $key};
+ $class = $FIELD_METADATA{$key}->[0];
+ }
+ die "Unknown field '$key' in '$string'" unless $class;
+
+ # replace __CurrentUser__ with id
+ $value = $self->CurrentUser->id if $value eq '__CurrentUser__';
+
+
+ unless( $dispatch{ $class } ) {
+ die "No dispatch method for class '$class'"
+ }
+ my $sub = $dispatch{ $class };
+
- $sub->( $self, $key, $op, $value,
++ my @res; my $bundle_with;
++ if ( $class eq 'WATCHERFIELD' && $key ne 'Owner' && !$negative_op && (!$null_op || $subkey) ) {
++ if ( !$sub_tree{$key} ) {
++ $sub_tree{$key} = [ ('(')x$depth, \@res ];
++ } else {
++ $bundle_with = $self->_check_bundling_possibility( $string, @{ $sub_tree{$key} } );
++ if ( $sub_tree{$key}[-1] eq '(' ) {
++ push @{ $sub_tree{$key} }, \@res;
++ }
++ }
++ }
++
++ # Remove our aggregator from subtrees where our condition didn't get added
++ pop @$_ foreach grep @$_ && $_->[-1] =~ /^(?:AND|OR)$/i, values %sub_tree;
++
++ # A reference to @res may be pushed onto $sub_tree{$key} from
++ # above, and we fill it here.
++ @res = $sub->( $self, $key, $op, $value,
++ SUBCLAUSE => '', # don't need anymore
+ ENTRYAGGREGATOR => $ea,
+ SUBKEY => $subkey,
++ BUNDLE => $bundle_with,
+ );
+ $ea = '';
+ };
+ RT::SQL::Parse($string, \%callback);
+}
+
+sub FromSQL {
+ my ($self,$query) = @_;
+
+ {
+ # preserve first_row and show_rows across the CleanSlate
+ local ($self->{'first_row'}, $self->{'show_rows'}, $self->{_sql_looking_at});
+ $self->CleanSlate;
+ $self->_InitSQL();
+ }
+
+ return (1, $self->loc("No Query")) unless $query;
+
+ $self->{_sql_query} = $query;
+ eval {
+ local $self->{parsing_ticketsql} = 1;
+ $self->_parser( $query );
+ };
+ if ( $@ ) {
+ $RT::Logger->error( $@ );
+ return (0, $@);
+ }
+
+ # We only want to look at EffectiveId's (mostly) for these searches.
+ unless ( $self->{_sql_looking_at}{effectiveid} ) {
+ # instead of EffectiveId = id we do IsMerged IS NULL
+ $self->Limit(
+ FIELD => 'IsMerged',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ ENTRYAGGREGATOR => 'AND',
+ QUOTEVALUE => 0,
+ );
+ }
+ unless ( $self->{_sql_looking_at}{type} ) {
+ $self->Limit( FIELD => 'Type', VALUE => 'ticket' );
+ }
+
+ # We don't want deleted tickets unless 'allow_deleted_search' is set
+ unless( $self->{'allow_deleted_search'} ) {
+ $self->Limit(
+ FIELD => 'Status',
+ OPERATOR => '!=',
+ VALUE => 'deleted',
+ );
+ }
+
+ # set SB's dirty flag
+ $self->{'must_redo_search'} = 1;
+ $self->{'RecalcTicketLimits'} = 0;
+
+ return (1, $self->loc("Valid Query"));
+}
+
+=head2 Query
+
+Returns the last string passed to L</FromSQL>.
+
+=cut
+
+sub Query {
+ my $self = shift;
+ return $self->{_sql_query};
+}
+
++sub _check_bundling_possibility {
++ my $self = shift;
++ my $string = shift;
++ my @list = reverse @_;
++ while (my $e = shift @list) {
++ next if $e eq '(';
++ if ( lc($e) eq 'and' ) {
++ return undef;
++ }
++ elsif ( lc($e) eq 'or' ) {
++ return shift @list;
++ }
++ else {
++ # should not happen
++ $RT::Logger->error(
++ "Joins optimization failed when parsing '$string'. It's bug in RT, contact Best Practical"
++ );
++ die "Internal error. Contact your system administrator.";
++ }
++ }
++ return undef;
++}
+
=head2 NewItem
diff --cc share/html/Prefs/Quicksearch.html
index 5330ac6,cb4292a..81757b9
--- a/share/html/Prefs/Quicksearch.html
+++ b/share/html/Prefs/Quicksearch.html
@@@ -57,12 -57,19 +57,20 @@@
% unless ($unwanted->{$queue->Name}) {
checked="checked"
% }
-/><%$queue->Name%><% $queue->Description ? ': '.$queue->Description : '' %></li>
+/>
+<label for="Want-<%$queue->Name%>"><%$queue->Name%><% $queue->Description ? ': '.$queue->Description : '' %></label>
+</li>
% }
</ul>
- <& /Elements/Submit, CheckAll => 1, ClearAll => 1, Caption => loc("Save Changes"), Label => loc('Save'), Name => 'Save'&>
-
+ <& /Elements/Submit,
+ Caption => loc("Save Changes"),
+ Label => loc('Save'),
+ Name => 'Save',
+ Reset => 1,
+ CheckAll => 1,
+ ClearAll => 1,
+ CheckboxNameRegex => '/^Want-/',
+ &>
</form>
diff --cc share/html/SelfService/Prefs.html
index 3f1cca0,6478ef2..ebe42ac
--- a/share/html/SelfService/Prefs.html
+++ b/share/html/SelfService/Prefs.html
@@@ -57,9 -74,10 +74,10 @@@
&>
</&>
+ </td></tr></table>
<br />
<& /Elements/Submit, Label => loc('Save Changes') &>
- </form>
+</form>
<%INIT>
diff --cc t/99-policy.t
index 717af8c,1980e34..a8e512c
--- a/t/99-policy.t
+++ b/t/99-policy.t
@@@ -119,7 -97,5 +119,10 @@@ check( $_, exec => -1
check( $_, exec => -1 )
for grep {m{^t/data/}} @files;
+ check( $_, exec => -1, bps_tag => -1 )
+ for grep {m{^etc/upgrade/[^/]+/}} @files;
++
+check( $_, warnings => 1, strict => 1, compile => 1, no_tabs => 1 )
+ for grep {m{^etc/upgrade/.*/content$}} @files;
+
+done_testing;
-----------------------------------------------------------------------
More information about the Rt-commit
mailing list