%# BEGIN BPS TAGGED BLOCK {{{
%#
%# COPYRIGHT:
%#
%# This software is Copyright (c) 1996-2026 Best Practical Solutions, LLC
%#                                          <sales@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 }}}
%# REST/1.0/search/dhandler
%#
<%ARGS>
$query
$format => undef
$orderby => undef
$fields => undef
</%ARGS>
<%INIT>
my $type = $m->dhandler_arg;
my ( $status, $output );

if ( $type =~ /^(ticket|queue|user|group)$/i ) {
    $status = "200 Ok";
    $output = '';
    my $type = lc $1;

    if (
        $type eq 'user'
        && !$session{CurrentUser}->HasRight(
            Object => $RT::System,
            Right  => 'AdminUsers',
        )
      )
    {

        $status = "403 Forbidden";
        $output = "Permission denied";
        goto OUTPUT;
    }

    my $class = 'RT::' . ucfirst $type . 's';
    my $objects = $class->new( $session{CurrentUser} );

    # Parse and validate any field specifications.
    require RT::Interface::REST;
    my $field = RT::Interface::REST->field_spec;
    my ( %fields, @fields );
    if ($fields) {
        $format ||= "l";
        unless ( $fields =~ /^(?:$field,)*$field$/ ) {
            $status = "400 Bad Request";
            $output = "Invalid field specification: $fields";
            goto OUTPUT;
        }
        @fields = map lc, split /\s*,\s*/, $fields;
        @fields{@fields} = ();
        unless ( exists $fields{id} ) {
            unshift @fields, "id";
            $fields{id} = ();
        }
    }

    $format ||= "s";
    if ( $format !~ /^[isl]$/ ) {
        $status = "400 Bad request";
        $output = "Unknown listing format: $format. (Use i, s, or l.)\n";
        goto OUTPUT;
    }

    my ( $n, $s );
    $n = 0;
    my @output;


    if ( $type eq 'group' ) {
        $objects->LimitToUserDefinedGroups;
    }

    if ( defined $query && length $query ) {
        if ( $type eq 'ticket' ) {
            my ( $n, $s );
            eval { ( $n, $s ) = $objects->FromSQL($query); };
            if ( $@ || $n == 0 ) {
                $s ||= $@;
                $status = "400 Bad request";
                $output = "Invalid query: '$s'.\n";
                goto OUTPUT;
            }
        }
        else {
            require Text::ParseWords;
            my ( $field, $op, $value ) = Text::ParseWords::shellwords($query);
            if ( $op !~
                /^(?:[!<>]?=|[<>]|(NOT )?LIKE|STARTSWITH|ENDSWITH|MATCHES)$/i )
            {
                $status = "400 Bad Request";
                $output = "Invalid operator specification: $op";
                goto OUTPUT;
            }

            if ( ! $search_whitelist{$type}{lc $field} ) {
                $status = "400 Bad Request";
                $output = "Invalid field specification: $field";
                goto OUTPUT;
            }


            if ( $field && $op && defined $value ) {
                if ( $field eq 'Disabled' ) {
                    if ($value) {
                        if ( $type eq 'queue' ) {
                            $objects->FindAllRows;
                            $objects->Limit(
                                FIELD    => $field,
                                OPERATOR => uc $op,
                                VALUE    => $value
                            );
                        }
                        else {
                            $objects->LimitToDeleted;
                        }
                    }
                    else {
                        if ( $type eq 'queue' ) {
                            $objects->UnLimit;
                        }
                        else {
                            $objects->LimitToEnabled;
                        }
                    }
                }
                else {
                    $objects->Limit(
                        FIELD    => $field,
                        OPERATOR => uc $op,
                        VALUE    => $value,
                        CASESENSITIVE => 0,
                    );
                }
            }
            else {
                $output = "Invalid query specification: $query";
                goto OUTPUT;
            }
        }
    }
    else {
        if ( $type eq 'queue' ) {
            $objects->UnLimit;
        }
        elsif ( $type eq 'user' ) {
            $objects->LimitToPrivileged;
        }
    }

    if ($orderby) {
        my ( $order, $field ) = $orderby =~ /^([\+\-])?(.+)/;
        $order = $order && $order eq '-' ? 'DESC' : 'ASC';
        $objects->OrderBy( FIELD => $field, ORDER => $order );
    }

    while ( my $object = $objects->Next ) {
        next if $type eq 'user' && ( $object->id == RT->SystemUser->id || $object->id == RT->Nobody->id );
        $n++;

        my $id = $object->Id;
        if ( $format eq "i" ) {
            $output .= "$type/" . $id . "\n";
        }
        elsif ( $format eq "s" ) {
            if ($fields) {
                my $result = $m->comp(
                    "/REST/1.0/Forms/$type/default",
                    id     => $id,
                    format => $format,
                    fields => \%fields
                );
                my ( $notes, $order, $key_values, $errors ) = @$result;

                # If it's the first time through, add our header
                if ( $n == 1 ) {
                    $output .= join( "\t", @$order ) . "\n";
                }

                # Cut off the annoying $type/ before the id;
                $key_values->{'id'} = $id;
                $output .= join(
                    "\t",
                    map {
                        ref $key_values->{$_} eq 'ARRAY'
                          ? join( ', ', @{ $key_values->{$_} } )
                          : $key_values->{$_}
                      } @$order
                ) . "\n";
            }
            else {
                if ( $type eq 'ticket' ) {
                    $output .= $object->Id . ": " . $object->Subject . "\n";
                }
                else {
                    $output .= $object->Id . ": " . $object->Name . "\n";
                }
            }
        }
        else {
            my $d = $m->comp(
                "/REST/1.0/Forms/$type/default",
                id     => $id,
                format => $format,
                fields => \%fields
            );
            my ( $c, $o, $k, $e ) = @$d;
            push @output, [ $c, $o, $k ];
        }
    }
    if ( $n == 0 && $format ne "i" ) {
        $output = "No matching results.\n";
    }

    $output = form_compose( \@output ) if @output;
}
else {
    $status = "500 Server Error";
    $output = "Unsupported object type.";
    goto OUTPUT;
}

OUTPUT:
$m->out("RT/". $RT::VERSION . " " . $status ."\n\n");
$m->out($output );
</%INIT>

<%ONCE>
my %search_whitelist = (
    queue => {
        map { lc $_ => 1 }
          grep { $RT::Record::_TABLE_ATTR->{'RT::Queue'}{$_}{read} }
          keys %{ $RT::Record::_TABLE_ATTR->{'RT::Queue'} }
    },
    user => {
        disabled => 1,
        map { lc $_ => 1 }
          grep { $RT::Record::_TABLE_ATTR->{'RT::User'}{$_}{read} }
          keys %{ $RT::Record::_TABLE_ATTR->{'RT::User'} }
    },
    group => {
        disabled => 1,
        map { lc $_ => 1 }
          grep { $RT::Record::_TABLE_ATTR->{'RT::Group'}{$_}{read} }
          keys %{ $RT::Record::_TABLE_ATTR->{'RT::Group'} }
    }
);

</%ONCE>

