diff options
author | cvs2git <cvs2git> | 2004-12-02 10:18:36 +0000 |
---|---|---|
committer | cvs2git <cvs2git> | 2004-12-02 10:18:36 +0000 |
commit | d8503dba9fa96923fb725dfc7f74042cec0ebfad (patch) | |
tree | e6acbc9f632491cb9c6a523c896ccc5ed38a6a83 | |
parent | c582e92888b4a5553e1b4e5214cf35217e4a0cf0 (diff) | |
parent | 2e01e767e3ec53eca7193e4498a449d9d92b4f89 (diff) |
This commit was manufactured by cvs2svn to create tag 'BEFORE_RT_3_2_2'.BEFORE_RT_3_2_2
-rw-r--r-- | rt/FREESIDE_MODIFIED | 20 | ||||
-rwxr-xr-x | rt/bin/rt | 1816 | ||||
-rw-r--r-- | rt/config.layout.in (renamed from rt/config.layout) | 23 | ||||
-rw-r--r-- | rt/etc/RT_SiteConfig.pm | 12 | ||||
-rw-r--r-- | rt/etc/schema.Oracle | 354 | ||||
-rw-r--r-- | rt/html/Elements/Footer | 2 | ||||
-rw-r--r-- | rt/html/Elements/Header | 32 | ||||
-rw-r--r-- | rt/html/Elements/PageLayout | 17 | ||||
-rw-r--r-- | rt/html/Elements/SimpleSearch | 2 | ||||
-rw-r--r-- | rt/html/Elements/Tabs | 7 | ||||
-rw-r--r-- | rt/html/NoAuth/images/small-logo.png | bin | 0 -> 4887 bytes | |||
-rw-r--r-- | rt/html/NoAuth/webrt.css | 69 | ||||
-rw-r--r-- | rt/html/Ticket/Elements/AddCustomers | 53 | ||||
-rw-r--r-- | rt/html/Ticket/Elements/EditCustomers | 67 | ||||
-rw-r--r-- | rt/html/Ticket/Elements/ShowCustomers | 40 | ||||
-rw-r--r-- | rt/html/Ticket/Elements/ShowSummary | 9 | ||||
-rw-r--r-- | rt/html/Ticket/Elements/Tabs | 2 | ||||
-rw-r--r-- | rt/html/Ticket/ModifyCustomers.html | 49 | ||||
-rw-r--r-- | rt/lib/RT/Interface/Web_Vendor.pm | 95 | ||||
-rw-r--r-- | rt/lib/RT/URI/freeside.pm | 260 | ||||
-rw-r--r-- | rt/sbin/rt-setup-database | 619 | ||||
-rw-r--r-- | rt/sbin/rt-setup-database.in | 5 | ||||
-rw-r--r-- | rt/sbin/rt-test-dependencies | 278 |
23 files changed, 740 insertions, 3091 deletions
diff --git a/rt/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED new file mode 100644 index 000000000..67bcfecb8 --- /dev/null +++ b/rt/FREESIDE_MODIFIED @@ -0,0 +1,20 @@ +sbin/rt-setup-database +sbin/rt-setup-database.in +config.layout +config.layout.in +etc/RT_SiteConfig.pm +lib/RT/Interface/Web_Vendor.pm +lib/RT/URI/freeside.pm +html/Elements/Header +html/Elements/PageLayout +html/Elements/SimpleSearch +html/Elements/Tabs +html/Elements/Footer +html/Ticket/Elements/AddCustomers +html/Ticket/Elements/EditCustomers +html/Ticket/Elements/ShowCustomers +html/Ticket/Elements/ShowSummary +html/Ticket/Elements/Tabs +html/Ticket/ModifyCustomers.html +html/NoAuth/images/small-logo.png +html/NoAuth/webrt.css diff --git a/rt/bin/rt b/rt/bin/rt deleted file mode 100755 index d9f8a3f05..000000000 --- a/rt/bin/rt +++ /dev/null @@ -1,1816 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# -# (Except where explictly superceded by other copyright notices) -# -# 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. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK - -use strict; - -# This program is intentionally written to have as few non-core module -# dependencies as possible. It should stay that way. - -use Cwd; -use LWP; -use HTTP::Request::Common; - -# We derive configuration information from hardwired defaults, dotfiles, -# and the RT* environment variables (in increasing order of precedence). -# Session information is stored in ~/.rt_sessions. - -my $VERSION = 0.02; -my $HOME = eval{(getpwuid($<))[7]} - || $ENV{HOME} || $ENV{LOGDIR} || $ENV{HOMEPATH} - || "."; -my %config = ( - ( - debug => 0, - user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, - passwd => undef, - server => 'http://localhost/rt/', - ), - config_from_file($ENV{RTCONFIG} || ".rtrc"), - config_from_env() -); -my $session = new Session("$HOME/.rt_sessions"); -my $REST = "$config{server}/REST/1.0"; - -sub whine; -sub DEBUG { warn @_ if $config{debug} >= shift } - -# These regexes are used by command handlers to parse arguments. -# (XXX: Ask Autrijus how i18n changes these definitions.) - -my $name = '[\w.-]+'; -my $field = '[a-zA-Z][a-zA-Z0-9_-]*'; -my $label = '[a-zA-Z0-9@_.+-]+'; -my $labels = "(?:$label,)*$label"; -my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; - -# Our command line looks like this: -# -# rt <action> [options] [arguments] -# -# We'll parse just enough of it to decide upon an action to perform, and -# leave the rest to per-action handlers to interpret appropriately. - -my %handlers = ( -# handler => [ ...aliases... ], - version => ["version", "ver"], - logout => ["logout"], - help => ["help", "man"], - show => ["show", "cat"], - edit => ["create", "edit", "new", "ed"], - list => ["search", "list", "ls"], - comment => ["comment", "correspond"], - link => ["link", "ln"], - merge => ["merge"], - grant => ["grant", "revoke"], -); - -# Once we find and call an appropriate handler, we're done. - -my (%actions, $action); -foreach my $fn (keys %handlers) { - foreach my $alias (@{ $handlers{$fn} }) { - $actions{$alias} = \&{"$fn"}; - } -} -if (@ARGV && exists $actions{$ARGV[0]}) { - $action = shift @ARGV; -} -$actions{$action || "help"}->($action || ()); -exit; - -# Handler functions. -# ------------------ -# -# The following subs are handlers for each entry in %actions. - -sub version { - print "rt $VERSION\n"; -} - -sub logout { - submit("$REST/logout") if defined $session->cookie; -} - -sub help { - my ($action, $type) = @_; - my (%help, $key); - - # What help topics do we know about? - local $/ = undef; - foreach my $item (@{ Form::parse(<DATA>) }) { - my $title = $item->[2]{Title}; - my @titles = ref $title eq 'ARRAY' ? @$title : $title; - - foreach $title (grep $_, @titles) { - $help{$title} = $item->[2]{Text}; - } - } - - # What does the user want help with? - undef $action if ($action && $actions{$action} eq \&help); - unless ($action || $type) { - # If we don't know, we'll look for clues in @ARGV. - foreach (@ARGV) { - if (exists $help{$_}) { $key = $_; last; } - } - unless ($key) { - # Tolerate possibly plural words. - foreach (@ARGV) { - if ($_ =~ s/s$// && exists $help{$_}) { $key = $_; last; } - } - } - } - - if ($type && $action) { - $key = "$type.$action"; - } - $key ||= $type || $action || "introduction"; - - # Find a suitable topic to display. - while (!exists $help{$key}) { - if ($type && $action) { - if ($key eq "$type.$action") { $key = $action; } - elsif ($key eq $action) { $key = $type; } - else { $key = "introduction"; } - } - else { - $key = "introduction"; - } - } - - print STDERR $help{$key}, "\n\n"; -} - -# Displays a list of objects that match some specified condition. - -sub list { - my ($q, $type, %data, $orderby); - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-t$/) { - $bad = 1, last unless defined($type = get_type_argument()); - } - elsif (/^-S$/) { - $bad = 1, last unless get_var_argument(\%data); - } - elsif (/^-o$/) { - $orderby = shift @ARGV; - } - elsif (/^-([isl])$/) { - $data{format} = $1; - } - elsif (/^-f$/) { - if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { - whine "No valid field list in '-f $ARGV[0]'."; - $bad = 1; last; - } - $data{fields} = shift @ARGV; - } - elsif (!defined $q && !/^-/) { - $q = $_; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - $type ||= "ticket"; - unless ($type && defined $q) { - my $item = $type ? "query string" : "object type"; - whine "No $item specified."; - $bad = 1; - } - return help("list", $type) if $bad; - - my $r = submit("$REST/search/$type", { query => $q, %data, orderby => $orderby || "" }); - print $r->content; -} - -# Displays selected information about a single object. - -sub show { - my ($type, @objects, %data); - my $slurped = 0; - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-t$/) { - $bad = 1, last unless defined($type = get_type_argument()); - } - elsif (/^-S$/) { - $bad = 1, last unless get_var_argument(\%data); - } - elsif (/^-([isl])$/) { - $data{format} = $1; - } - elsif (/^-$/ && !$slurped) { - chomp(my @lines = <STDIN>); - foreach (@lines) { - unless (is_object_spec($_, $type)) { - whine "Invalid object on STDIN: '$_'."; - $bad = 1; last; - } - push @objects, $_; - } - $slurped = 1; - } - elsif (/^-f$/) { - if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { - whine "No valid field list in '-f $ARGV[0]'."; - $bad = 1; last; - } - $data{fields} = shift @ARGV; - } - elsif (my $spec = is_object_spec($_, $type)) { - push @objects, $spec; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - unless (@objects) { - whine "No objects specified."; - $bad = 1; - } - return help("show", $type) if $bad; - - my $r = submit("$REST/show", { id => \@objects, %data }); - print $r->content; -} - -# To create a new object, we ask the server for a form with the defaults -# filled in, allow the user to edit it, and send the form back. -# -# To edit an object, we must ask the server for a form representing that -# object, make changes requested by the user (either on the command line -# or interactively via $EDITOR), and send the form back. - -sub edit { - my ($action) = @_; - my (%data, $type, @objects); - my ($cl, $text, $edit, $input, $output); - - use vars qw(%set %add %del); - %set = %add = %del = (); - my $slurped = 0; - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-e$/) { $edit = 1 } - elsif (/^-i$/) { $input = 1 } - elsif (/^-o$/) { $output = 1 } - elsif (/^-t$/) { - $bad = 1, last unless defined($type = get_type_argument()); - } - elsif (/^-S$/) { - $bad = 1, last unless get_var_argument(\%data); - } - elsif (/^-$/ && !($slurped || $input)) { - chomp(my @lines = <STDIN>); - foreach (@lines) { - unless (is_object_spec($_, $type)) { - whine "Invalid object on STDIN: '$_'."; - $bad = 1; last; - } - push @objects, $_; - } - $slurped = 1; - } - elsif (/^set$/i) { - my $vars = 0; - - while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/) { - my ($key, $op, $val) = ($1, $2, $3); - my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del; - - vpush($hash, lc $key, $val); - shift @ARGV; - $vars++; - } - unless ($vars) { - whine "No variables to set."; - $bad = 1; last; - } - $cl = $vars; - } - elsif (/^(?:add|del)$/i) { - my $vars = 0; - my $hash = ($_ eq "add") ? \%add : \%del; - - while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/) { - my ($key, $val) = ($1, $2); - - vpush($hash, lc $key, $val); - shift @ARGV; - $vars++; - } - unless ($vars) { - whine "No variables to set."; - $bad = 1; last; - } - $cl = $vars; - } - elsif (my $spec = is_object_spec($_, $type)) { - push @objects, $spec; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - if ($action =~ /^ed(?:it)?$/) { - unless (@objects) { - whine "No objects specified."; - $bad = 1; - } - } - else { - if (@objects) { - whine "You shouldn't specify objects as arguments to $action."; - $bad = 1; - } - unless ($type) { - whine "What type of object do you want to create?"; - $bad = 1; - } - @objects = ("$type/new"); - } - return help($action, $type) if $bad; - - # We need a form to make changes to. We usually ask the server for - # one, but we can avoid that if we are fed one on STDIN, or if the - # user doesn't want to edit the form by hand, and the command line - # specifies only simple variable assignments. - - if ($input) { - local $/ = undef; - $text = <STDIN>; - } - elsif ($edit || %add || %del || !$cl) { - my $r = submit("$REST/show", { id => \@objects, format => 'l' }); - $text = $r->content; - } - - # If any changes were specified on the command line, apply them. - if ($cl) { - if ($text) { - # We're updating forms from the server. - my $forms = Form::parse($text); - - foreach my $form (@$forms) { - my ($c, $o, $k, $e) = @$form; - my ($key, $val); - - next if ($e || !@$o); - - local %add = %add; - local %del = %del; - local %set = %set; - - # Make changes to existing fields. - foreach $key (@$o) { - if (exists $add{lc $key}) { - $val = delete $add{lc $key}; - vpush($k, $key, $val); - $k->{$key} = vsplit($k->{$key}) if $val =~ /[,\n]/; - } - if (exists $del{lc $key}) { - $val = delete $del{lc $key}; - my %val = map {$_=>1} @{ vsplit($val) }; - $k->{$key} = vsplit($k->{$key}); - @{$k->{$key}} = grep {!exists $val{$_}} @{$k->{$key}}; - } - if (exists $set{lc $key}) { - $k->{$key} = delete $set{lc $key}; - } - } - - # Then update the others. - foreach $key (keys %set) { vpush($k, $key, $set{$key}) } - foreach $key (keys %add) { - vpush($k, $key, $add{$key}); - $k->{$key} = vsplit($k->{$key}); - } - push @$o, (keys %add, keys %set); - } - - $text = Form::compose($forms); - } - else { - # We're rolling our own set of forms. - my @forms; - foreach (@objects) { - my ($type, $ids, $args) = - m{^($name)/($idlist|$labels)(?:(/.*))?$}o; - - $args ||= ""; - foreach my $obj (expand_list($ids)) { - my %set = (%set, id => "$type/$obj$args"); - push @forms, ["", [keys %set], \%set]; - } - } - $text = Form::compose(\@forms); - } - } - - if ($output) { - print $text; - exit; - } - - my $synerr = 0; - -EDIT: - # We'll let the user edit the form before sending it to the server, - # unless we have enough information to submit it non-interactively. - if ($edit || (!$input && !$cl)) { - my $newtext = vi($text); - # We won't resubmit a bad form unless it was changed. - $text = ($synerr && $newtext eq $text) ? undef : $newtext; - } - - if ($text) { - my $r = submit("$REST/edit", {content => $text, %data}); - if ($r->code == 409) { - # If we submitted a bad form, we'll give the user a chance - # to correct it and resubmit. - if ($edit || (!$input && !$cl)) { - $text = $r->content; - $synerr = 1; - goto EDIT; - } - else { - print $r->content; - exit -1; - } - } - print $r->content; - } -} - -# We roll "comment" and "correspond" into the same handler. - -sub comment { - my ($action) = @_; - my (%data, $id, @files, @bcc, @cc, $msg, $wtime, $edit); - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-e$/) { - $edit = 1; - } - elsif (/^-[abcmw]$/) { - unless (@ARGV) { - whine "No argument specified with $_."; - $bad = 1; last; - } - - if (/-a/) { - unless (-f $ARGV[0] && -r $ARGV[0]) { - whine "Cannot read attachment: '$ARGV[0]'."; - exit -1; - } - push @files, shift @ARGV; - } - elsif (/-([bc])/) { - my $a = $_ eq "-b" ? \@bcc : \@cc; - @$a = split /\s*,\s*/, shift @ARGV; - } - elsif (/-m/) { $msg = shift @ARGV } - elsif (/-w/) { $wtime = shift @ARGV } - } - elsif (!$id && m|^(?:ticket/)?($idlist)$|) { - $id = $1; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - unless ($id) { - whine "No object specified."; - $bad = 1; - } - return help($action, "ticket") if $bad; - - my $form = [ - "", - [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Text" ], - { - Ticket => $id, - Action => $action, - Cc => [ @cc ], - Bcc => [ @bcc ], - Attachment => [ @files ], - TimeWorked => $wtime || '', - Text => $msg || '', - } - ]; - - my $text = Form::compose([ $form ]); - - if ($edit || !$msg) { - my $error = 0; - my ($c, $o, $k, $e); - - do { - my $ntext = vi($text); - exit if ($error && $ntext eq $text); - $text = $ntext; - $form = Form::parse($text); - $error = 0; - - ($c, $o, $k, $e) = @{ $form->[0] }; - if ($e) { - $error = 1; - $c = "# Syntax error."; - goto NEXT; - } - elsif (!@$o) { - exit; - } - @files = @{ vsplit($k->{Attachment}) }; - - NEXT: - $text = Form::compose([[$c, $o, $k, $e]]); - } while ($error); - } - - my $i = 1; - foreach my $file (@files) { - $data{"attachment_$i"} = bless([ $file ], "Attachment"); - $i++; - } - $data{content} = $text; - - my $r = submit("$REST/ticket/comment/$id", \%data); - print $r->content; -} - -# Merge one ticket into another. - -sub merge { - my @id; - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^\d+$/) { - push @id, $_; - } - else { - whine "Unrecognised argument: '$_'."; - $bad = 1; last; - } - } - - unless (@id == 2) { - my $evil = @id > 2 ? "many" : "few"; - whine "Too $evil arguments specified."; - $bad = 1; - } - return help("merge", "ticket") if $bad; - - my $r = submit("$REST/ticket/merge/$id[0]", {into => $id[1]}); - print $r->content; -} - -# Link one ticket to another. - -sub link { - my ($bad, $del, %data) = (0, 0, ()); - my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo - ReferredToBy HasMember MemberOf); - - while (@ARGV && $ARGV[0] =~ /^-/) { - $_ = shift @ARGV; - - if (/^-d$/) { - $del = 1; - } - else { - whine "Unrecognised option: '$_'."; - $bad = 1; last; - } - } - - if (@ARGV == 3) { - my ($from, $rel, $to) = @ARGV; - if ($from !~ /^\d+$/ || $to !~ /^\d+$/) { - my $bad = $from =~ /^\d+$/ ? $to : $from; - whine "Invalid ticket ID '$bad' specified."; - $bad = 1; - } - unless (exists $ltypes{lc $rel}) { - whine "Invalid relationship '$rel' specified."; - $bad = 1; - } - %data = (id => $from, rel => $rel, to => $to, del => $del); - } - else { - my $bad = @ARGV < 3 ? "few" : "many"; - whine "Too $bad arguments specified."; - $bad = 1; - } - return help("link", "ticket") if $bad; - - my $r = submit("$REST/ticket/link", \%data); - print $r->content; -} - -# Grant/revoke a user's rights. - -sub grant { - my ($cmd) = @_; - - my $revoke = 0; - while (@ARGV) { - } - - $revoke = 1 if $cmd->{action} eq 'revoke'; -} - -# Client <-> Server communication. -# -------------------------------- -# -# This function composes and sends an HTTP request to the RT server, and -# interprets the response. It takes a request URI, and optional request -# data (a string, or a reference to a set of key-value pairs). - -sub submit { - my ($uri, $content) = @_; - my ($req, $data); - my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1); - - # Did the caller specify any data to send with the request? - $data = []; - if (defined $content) { - unless (ref $content) { - # If it's just a string, make sure LWP handles it properly. - # (By pretending that it's a file!) - $content = [ content => [undef, "", Content => $content] ]; - } - elsif (ref $content eq 'HASH') { - my @data; - foreach my $k (keys %$content) { - if (ref $content->{$k} eq 'ARRAY') { - foreach my $v (@{ $content->{$k} }) { - push @data, $k, $v; - } - } - else { push @data, $k, $content->{$k} } - } - $content = \@data; - } - $data = $content; - } - - # Should we send authentication information to start a new session? - if (!defined $session->cookie) { - push @$data, ( user => $config{user} ); - push @$data, ( pass => $config{passwd} || read_passwd() ); - } - - # Now, we construct the request. - if (@$data) { - $req = POST($uri, $data, Content_Type => 'form-data'); - } - else { - $req = GET($uri); - } - $session->add_cookie_header($req); - - # Then we send the request and parse the response. - DEBUG(3, $req->as_string); - my $res = $ua->request($req); - DEBUG(3, $res->as_string); - - if ($res->is_success) { - # The content of the response we get from the RT server consists - # of an HTTP-like status line followed by optional header lines, - # a blank line, and arbitrary text. - - my ($head, $text) = split /\n\n/, $res->content, 2; - my ($status, @headers) = split /\n/, $head; - $text =~ s/\n*$/\n/; - - # "RT/3.0.1 401 Credentials required" - if ($status !~ m#^RT/\d+(?:\.\d+)+(?:-?\w+)? (\d+) ([\w\s]+)$#) { - warn "rt: Malformed RT response from $config{server}.\n"; - warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3; - exit -1; - } - - # Our caller can pretend that the server returned a custom HTTP - # response code and message. (Doing that directly is apparently - # not sufficiently portable and uncomplicated.) - $res->code($1); - $res->message($2); - $res->content($text); - $session->update($res) if ($res->is_success || $res->code != 401); - - if (!$res->is_success) { - # We can deal with authentication failures ourselves. Either - # we sent invalid credentials, or our session has expired. - if ($res->code == 401) { - my %d = @$data; - if (exists $d{user}) { - warn "rt: Incorrect username or password.\n"; - exit -1; - } - elsif ($req->header("Cookie")) { - # We'll retry the request with credentials, unless - # we only wanted to logout in the first place. - $session->delete; - return submit(@_) unless $uri eq "$REST/logout"; - } - } - # Conflicts should be dealt with by the handler and user. - # For anything else, we just die. - elsif ($res->code != 409) { - warn "rt: ", $res->content; - exit; - } - } - } - else { - warn "rt: Server error: ", $res->message, " (", $res->code, ")\n"; - exit -1; - } - - return $res; -} - -# Session management. -# ------------------- -# -# Maintains a list of active sessions in the ~/.rt_sessions file. -{ - package Session; - my ($s, $u); - - # Initialises the session cache. - sub new { - my ($class, $file) = @_; - my $self = { - file => $file || "$HOME/.rt_sessions", - sids => { } - }; - - # The current session is identified by the currently configured - # server and user. - ($s, $u) = @config{"server", "user"}; - - bless $self, $class; - $self->load(); - - return $self; - } - - # Returns the current session cookie. - sub cookie { - my ($self) = @_; - my $cookie = $self->{sids}{$s}{$u}; - return defined $cookie ? "RT_SID=$cookie" : undef; - } - - # Deletes the current session cookie. - sub delete { - my ($self) = @_; - delete $self->{sids}{$s}{$u}; - } - - # Adds a Cookie header to an outgoing HTTP request. - sub add_cookie_header { - my ($self, $request) = @_; - my $cookie = $self->cookie(); - - $request->header(Cookie => $cookie) if defined $cookie; - } - - # Extracts the Set-Cookie header from an HTTP response, and updates - # session information accordingly. - sub update { - my ($self, $response) = @_; - my $cookie = $response->header("Set-Cookie"); - - if (defined $cookie && $cookie =~ /^RT_SID=([0-9A-Fa-f]+);/) { - $self->{sids}{$s}{$u} = $1; - } - } - - # Loads the session cache from the specified file. - sub load { - my ($self, $file) = @_; - $file ||= $self->{file}; - local *F; - - open(F, $file) && do { - $self->{file} = $file; - my $sids = $self->{sids} = {}; - while (<F>) { - chomp; - next if /^$/ || /^#/; - next unless m#^https?://[^ ]+ \w+ [0-9A-Fa-f]+$#; - my ($server, $user, $cookie) = split / /, $_; - $sids->{$server}{$user} = $cookie; - } - return 1; - }; - return 0; - } - - # Writes the current session cache to the specified file. - sub save { - my ($self, $file) = shift; - $file ||= $self->{file}; - local *F; - - open(F, ">$file") && do { - my $sids = $self->{sids}; - foreach my $server (keys %$sids) { - foreach my $user (keys %{ $sids->{$server} }) { - my $sid = $sids->{$server}{$user}; - if (defined $sid) { - print F "$server $user $sid\n"; - } - } - } - close(F); - chmod 0600, $file; - return 1; - }; - return 0; - } - - sub DESTROY { - my $self = shift; - $self->save; - } -} - -# Form handling. -# -------------- -# -# Forms are RFC822-style sets of (field, value) specifications with some -# initial comments and interspersed blank lines allowed for convenience. -# Sets of forms are separated by --\n (in a cheap parody of MIME). -# -# Each form is parsed into an array with four elements: commented text -# at the start of the form, an array with the order of keys, a hash with -# key/value pairs, and optional error text if the form syntax was wrong. - -# Returns a reference to an array of parsed forms. -sub Form::parse { - my $state = 0; - my @forms = (); - my @lines = split /\n/, $_[0]; - my ($c, $o, $k, $e) = ("", [], {}, ""); - - LINE: - while (@lines) { - my $line = shift @lines; - - next LINE if $line eq ''; - - if ($line eq '--') { - # We reached the end of one form. We'll ignore it if it was - # empty, and store it otherwise, errors and all. - if ($e || $c || @$o) { - push @forms, [ $c, $o, $k, $e ]; - $c = ""; $o = []; $k = {}; $e = ""; - } - $state = 0; - } - elsif ($state != -1) { - if ($state == 0 && $line =~ /^#/) { - # Read an optional block of comments (only) at the start - # of the form. - $state = 1; - $c = $line; - while (@lines && $lines[0] =~ /^#/) { - $c .= "\n".shift @lines; - } - $c .= "\n"; - } - elsif ($state <= 1 && $line =~ /^($field):(?:\s+(.*))?$/) { - # Read a field: value specification. - my $f = $1; - my @v = ($2 || ()); - - # Read continuation lines, if any. - while (@lines && ($lines[0] eq '' || $lines[0] =~ /^\s+/)) { - push @v, shift @lines; - } - pop @v while (@v && $v[-1] eq ''); - - # Strip longest common leading indent from text. - my $ws = ""; - foreach my $ls (map {/^(\s+)/} @v[1..$#v]) { - $ws = $ls if (!$ws || length($ls) < length($ws)); - } - s/^$ws// foreach @v; - - push(@$o, $f) unless exists $k->{$f}; - vpush($k, $f, join("\n", @v)); - - $state = 1; - } - elsif ($line !~ /^#/) { - # We've found a syntax error, so we'll reconstruct the - # form parsed thus far, and add an error marker. (>>) - $state = -1; - $e = Form::compose([[ "", $o, $k, "" ]]); - $e.= $line =~ /^>>/ ? "$line\n" : ">> $line\n"; - } - } - else { - # We saw a syntax error earlier, so we'll accumulate the - # contents of this form until the end. - $e .= "$line\n"; - } - } - push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o); - - foreach my $l (keys %$k) { - $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY'); - } - - return \@forms; -} - -# Returns text representing a set of forms. -sub Form::compose { - my ($forms) = @_; - my @text; - - foreach my $form (@$forms) { - my ($c, $o, $k, $e) = @$form; - my $text = ""; - - if ($c) { - $c =~ s/\n*$/\n/; - $text = "$c\n"; - } - if ($e) { - $text .= $e; - } - elsif ($o) { - my @lines; - - foreach my $key (@$o) { - my ($line, $sp); - my $v = $k->{$key}; - my @values = ref $v eq 'ARRAY' ? @$v : $v; - - $sp = " "x(length("$key: ")); - $sp = " "x4 if length($sp) > 16; - - foreach $v (@values) { - if ($v =~ /\n/) { - $v =~ s/^/$sp/gm; - $v =~ s/^$sp//; - - if ($line) { - push @lines, "$line\n\n"; - $line = ""; - } - elsif (@lines && $lines[-1] !~ /\n\n$/) { - $lines[-1] .= "\n"; - } - push @lines, "$key: $v\n\n"; - } - elsif ($line && - length($line)+length($v)-rindex($line, "\n") >= 70) - { - $line .= ",\n$sp$v"; - } - else { - $line = $line ? "$line, $v" : "$key: $v"; - } - } - - $line = "$key:" unless @values; - if ($line) { - if ($line =~ /\n/) { - if (@lines && $lines[-1] !~ /\n\n$/) { - $lines[-1] .= "\n"; - } - $line .= "\n"; - } - push @lines, "$line\n"; - } - } - - $text .= join "", @lines; - } - else { - chomp $text; - } - push @text, $text; - } - - return join "\n--\n\n", @text; -} - -# Configuration. -# -------------- - -# Returns configuration information from the environment. -sub config_from_env { - my %env; - - foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER") { - if (exists $ENV{"RT$k"}) { - $env{lc $k} = $ENV{"RT$k"}; - } - } - - return %env; -} - -# Finds a suitable configuration file and returns information from it. -sub config_from_file { - my ($rc) = @_; - - if ($rc =~ m#^/#) { - # We'll use an absolute path if we were given one. - return parse_config_file($rc); - } - else { - # Otherwise we'll use the first file we can find in the current - # directory, or in one of its (increasingly distant) ancestors. - - my @dirs = split /\//, cwd; - while (@dirs) { - my $file = join('/', @dirs, $rc); - if (-r $file) { - return parse_config_file($file); - } - - # Remove the last directory component each time. - pop @dirs; - } - - # Still nothing? We'll fall back to some likely defaults. - for ("$HOME/$rc", "/etc/rt.conf") { - return parse_config_file($_) if (-r $_); - } - } - - return (); -} - -# Makes a hash of the specified configuration file. -sub parse_config_file { - my %cfg; - my ($file) = @_; - - open(CFG, $file) && do { - while (<CFG>) { - chomp; - next if (/^#/ || /^\s*$/); - - if (/^(user|passwd|server)\s+([^ ]+)$/) { - $cfg{$1} = $2; - } - else { - die "rt: $file:$.: unknown configuration directive.\n"; - } - } - }; - - return %cfg; -} - -# Helper functions. -# ----------------- - -sub whine { - my $sub = (caller(1))[3]; - $sub =~ s/^main:://; - warn "rt: $sub: @_\n"; - return; -} - -sub read_passwd { - eval 'require Term::ReadKey'; - if ($@) { - die "No password specified (and Term::ReadKey not installed).\n"; - } - - print "Password: "; - Term::ReadKey::ReadMode('noecho'); - chomp(my $passwd = Term::ReadKey::ReadLine(0)); - Term::ReadKey::ReadMode('restore'); - print "\n"; - - return $passwd; -} - -sub vi { - my ($text) = @_; - my $file = "/tmp/rt.form.$$"; - my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi"; - - local *F; - local $/ = undef; - - open(F, ">$file") || die "$file: $!\n"; print F $text; close(F); - system($editor, $file) && die "Couldn't run $editor.\n"; - open(F, $file) || die "$file: $!\n"; $text = <F>; close(F); - unlink($file); - - return $text; -} - -# Add a value to a (possibly multi-valued) hash key. -sub vpush { - my ($hash, $key, $val) = @_; - my @val = ref $val eq 'ARRAY' ? @$val : $val; - - if (exists $hash->{$key}) { - unless (ref $hash->{$key} eq 'ARRAY') { - my @v = $hash->{$key} ne '' ? $hash->{$key} : (); - $hash->{$key} = \@v; - } - push @{ $hash->{$key} }, @val; - } - else { - $hash->{$key} = $val; - } -} - -# "Normalise" a hash key that's known to be multi-valued. -sub vsplit { - my ($val) = @_; - my ($word, @words); - my @values = ref $val eq 'ARRAY' ? @$val : $val; - - foreach my $line (map {split /\n/} @values) { - # XXX: This should become a real parser, à la Text::ParseWords. - $line =~ s/^\s+//; - $line =~ s/\s+$//; - push @words, split /\s*,\s*/, $line; - } - - return \@words; -} - -sub expand_list { - my ($list) = @_; - my ($elt, @elts, %elts); - - foreach $elt (split /,/, $list) { - if ($elt =~ /^(\d+)-(\d+)$/) { push @elts, ($1..$2) } - else { push @elts, $elt } - } - - @elts{@elts}=(); - return sort {$a<=>$b} keys %elts; -} - -sub get_type_argument { - my $type; - - if (@ARGV) { - $type = shift @ARGV; - unless ($type =~ /^[A-Za-z0-9_.-]+$/) { - # We want whine to mention our caller, not us. - @_ = ("Invalid type '$type' specified."); - goto &whine; - } - } - else { - @_ = ("No type argument specified with -t."); - goto &whine; - } - - $type =~ s/s$//; # "Plural". Ugh. - return $type; -} - -sub get_var_argument { - my ($data) = @_; - - if (@ARGV) { - my $kv = shift @ARGV; - if (my ($k, $v) = $kv =~ /^($field)=(.*)$/) { - push @{ $data->{$k} }, $v; - } - else { - @_ = ("Invalid variable specification: '$kv'."); - goto &whine; - } - } - else { - @_ = ("No variable argument specified with -S."); - goto &whine; - } -} - -sub is_object_spec { - my ($spec, $type) = @_; - - $spec =~ s|^(?:$type/)?|$type/| if defined $type; - return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o); - return; -} - -__DATA__ - -Title: intro -Title: introduction -Text: - - ** THIS IS AN UNSUPPORTED PREVIEW RELEASE ** - ** PLEASE REPORT BUGS TO rt-bugs@fsck.com ** - - This is a command-line interface to RT 3. - - It allows you to interact with an RT server over HTTP, and offers an - interface to RT's functionality that is better-suited to automation - and integration with other tools. - - In general, each invocation of this program should specify an action - to perform on one or more objects, and any other arguments required - to complete the desired action. - - For more information: - - - rt help actions (a list of possible actions) - - rt help objects (how to specify objects) - - rt help usage (syntax information) - - - rt help config (configuration details) - - rt help examples (a few useful examples) - - rt help topics (a list of help topics) - --- - -Title: usage -Title: syntax -Text: - - Syntax: - - rt <action> [options] [arguments] - - Each invocation of this program must specify an action (e.g. "edit", - "create"), options to modify behaviour, and other arguments required - by the specified action. (For example, most actions expect a list of - numeric object IDs to act upon.) - - The details of the syntax and arguments for each action are given by - "rt help <action>". Some actions may be referred to by more than one - name ("create" is the same as "new", for example). - - Objects are identified by a type and an ID (which can be a name or a - number, depending on the type). For some actions, the object type is - implied (you can only comment on tickets); for others, the user must - specify it explicitly. See "rt help objects" for details. - - In syntax descriptions, mandatory arguments that must be replaced by - appropriate value are enclosed in <>, and optional arguments are - indicated by [] (for example, <action> and [options] above). - - For more information: - - - rt help objects (how to specify objects) - - rt help actions (a list of actions) - - rt help types (a list of object types) - --- - -Title: conf -Title: config -Title: configuration -Text: - - This program has two major sources of configuration information: its - configuration files, and the environment. - - The program looks for configuration directives in a file named .rtrc - (or $RTCONFIG; see below) in the current directory, and then in more - distant ancestors, until it reaches /. If no suitable configuration - files are found, it will also check for ~/.rtrc and /etc/rt.conf. - - Configuration directives: - - The following directives may occur, one per line: - - - server <URL> URL to RT server. - - user <username> RT username. - - passwd <passwd> RT user's password. - - Blank and #-commented lines are ignored. - - Environment variables: - - The following environment variables override any corresponding - values defined in configuration files: - - - RTUSER - - RTPASSWD - - RTSERVER - - RTDEBUG Numeric debug level. (Set to 3 for full logs.) - - RTCONFIG Specifies a name other than ".rtrc" for the - configuration file. - --- - -Title: objects -Text: - - Syntax: - - <type>/<id>[/<attributes>] - - Every object in RT has a type (e.g. "ticket", "queue") and a numeric - ID. Some types of objects can also be identified by name (like users - and queues). Furthermore, objects may have named attributes (such as - "ticket/1/history"). - - An object specification is like a path in a virtual filesystem, with - object types as top-level directories, object IDs as subdirectories, - and named attributes as further subdirectories. - - A comma-separated list of names, numeric IDs, or numeric ranges can - be used to specify more than one object of the same type. Note that - the list must be a single argument (i.e., no spaces). For example, - "user/root,1-3,5,7-10,ams" is a list of ten users; the same list - can also be written as "user/ams,root,1,2,3,5,7,8-20". - - Examples: - - ticket/1 - ticket/1/attachments - ticket/1/attachments/3 - ticket/1/attachments/3/content - ticket/1-3/links - ticket/1-3,5-7/history - - user/ams - user/ams/rights - user/ams,rai,1/rights - - For more information: - - - rt help <action> (action-specific details) - - rt help <type> (type-specific details) - --- - -Title: actions -Title: commands -Text: - - You can currently perform the following actions on all objects: - - - list (list objects matching some condition) - - show (display object details) - - edit (edit object details) - - create (create a new object) - - Each type may define actions specific to itself; these are listed in - the help item about that type. - - For more information: - - - rt help <action> (action-specific details) - - rt help types (a list of possible types) - --- - -Title: types -Text: - - You can currently operate on the following types of objects: - - - tickets - - users - - groups - - queues - - For more information: - - - rt help <type> (type-specific details) - - rt help objects (how to specify objects) - - rt help actions (a list of possible actions) - --- - -Title: ticket -Text: - - Tickets are identified by a numeric ID. - - The following generic operations may be performed upon tickets: - - - list - - show - - edit - - create - - In addition, the following ticket-specific actions exist: - - - link - - merge - - comment - - correspond - - Attributes: - - The following attributes can be used with "rt show" or "rt edit" - to retrieve or edit other information associated with tickets: - - links A ticket's relationships with others. - history All of a ticket's transactions. - history/type/<type> Only a particular type of transaction. - history/id/<id> Only the transaction of the specified id. - attachments A list of attachments. - attachments/<id> The metadata for an individual attachment. - attachments/<id>/content The content of an individual attachment. - --- - -Title: user -Title: group -Text: - - Users and groups are identified by name or numeric ID. - - The following generic operations may be performed upon them: - - - list - - show - - edit - - create - - In addition, the following type-specific actions exist: - - - grant - - revoke - - Attributes: - - The following attributes can be used with "rt show" or "rt edit" - to retrieve or edit other information associated with users and - groups: - - rights Global rights granted to this user. - rights/<queue> Queue rights for this user. - --- - -Title: queue -Text: - - Queues are identified by name or numeric ID. - - Currently, they can be subjected to the following actions: - - - show - - edit - - create - --- - -Title: logout -Text: - - Syntax: - - rt logout - - Terminates the currently established login session. You will need to - provide authentication credentials before you can continue using the - server. (See "rt help config" for details about authentication.) - --- - -Title: ls -Title: list -Title: search -Text: - - Syntax: - - rt <ls|list|search> [options] "query string" - - Displays a list of objects matching the specified conditions. - ("ls", "list", and "search" are synonyms.) - - Conditions are expressed in the SQL-like syntax used internally by - RT3. (For more information, see "rt help query".) The query string - must be supplied as one argument. - - (Right now, the server doesn't support listing anything but tickets. - Other types will be supported in future; this client will be able to - take advantage of that support without any changes.) - - Options: - - The following options control how much information is displayed - about each matching object: - - -i Numeric IDs only. (Useful for |rt edit -; see examples.) - -s Short description. - -l Longer description. - - In addition, - - -o +/-<field> Orders the returned list by the specified field. - -S var=val Submits the specified variable with the request. - -t type Specifies the type of object to look for. (The - default is "ticket".) - - Examples: - - rt ls "Priority > 5 and Status='new'" - rt ls -o +Subject "Priority > 5 and Status='new'" - rt ls -o -Created "Priority > 5 and Status='new'" - rt ls -i "Priority > 5"|rt edit - set status=resolved - rt ls -t ticket "Subject like '[PATCH]%'" - --- - -Title: show -Text: - - Syntax: - - rt show [options] <object-ids> - - Displays details of the specified objects. - - For some types, object information is further classified into named - attributes (for example, "1-3/links" is a valid ticket specification - that refers to the links for tickets 1-3). Consult "rt help <type>" - and "rt help objects" for further details. - - This command writes a set of forms representing the requested object - data to STDOUT. - - Options: - - - Read IDs from STDIN instead of the command-line. - -t type Specifies object type. - -f a,b,c Restrict the display to the specified fields. - -S var=val Submits the specified variable with the request. - - Examples: - - rt show -t ticket -f id,subject,status 1-3 - rt show ticket/3/attachments/29 - rt show ticket/3/attachments/29/content - rt show ticket/1-3/links - rt show -t user 2 - --- - -Title: new -Title: edit -Title: create -Text: - - Syntax: - - rt edit [options] <object-ids> set field=value [field=value] ... - add field=value [field=value] ... - del field=value [field=value] ... - - Edits information corresponding to the specified objects. - - If, instead of "edit", an action of "new" or "create" is specified, - then a new object is created. In this case, no numeric object IDs - may be specified, but the syntax and behaviour remain otherwise - unchanged. - - This command typically starts an editor to allow you to edit object - data in a form for submission. If you specified enough information - on the command-line, however, it will make the submission directly. - - The command line may specify field-values in three different ways. - "set" sets the named field to the given value, "add" adds a value - to a multi-valued field, and "del" deletes the corresponding value. - Each "field=value" specification must be given as a single argument. - - For some types, object information is further classified into named - attributes (for example, "1-3/links" is a valid ticket specification - that refers to the links for tickets 1-3). These attributes may also - be edited. Consult "rt help <type>" and "rt help object" for further - details. - - Options: - - - Read numeric IDs from STDIN instead of the command-line. - (Useful with rt ls ... | rt edit -; see examples below.) - -i Read a completed form from STDIN before submitting. - -o Dump the completed form to STDOUT instead of submitting. - -e Allows you to edit the form even if the command-line has - enough information to make a submission directly. - -S var=val - Submits the specified variable with the request. - -t type Specifies object type. - - Examples: - - # Interactive (starts $EDITOR with a form). - rt edit ticket/3 - rt create -t ticket - - # Non-interactive. - rt edit ticket/1-3 add cc=foo@example.com set priority=3 - rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved - rt edit ticket/4 set priority=3 owner=bar@example.com \ - add cc=foo@example.com bcc=quux@example.net - rt create -t ticket subject='new ticket' priority=10 \ - add cc=foo@example.com - --- - -Title: comment -Title: correspond -Text: - - Syntax: - - rt <comment|correspond> [options] <ticket-id> - - Adds a comment (or correspondence) to the specified ticket (the only - difference being that comments aren't sent to the requestors.) - - This command will typically start an editor and allow you to type a - comment into a form. If, however, you specified all the necessary - information on the command line, it submits the comment directly. - - (See "rt help forms" for more information about forms.) - - Options: - - -m <text> Specify comment text. - -a <file> Attach a file to the comment. (May be used more - than once to attach multiple files.) - -c <addrs> A comma-separated list of Cc addresses. - -b <addrs> A comma-separated list of Bcc addresses. - -w <time> Specify the time spent working on this ticket. - -e Starts an editor before the submission, even if - arguments from the command line were sufficient. - - Examples: - - rt comment -t 'Not worth fixing.' -a stddisclaimer.h 23 - --- - -Title: merge -Text: - - Syntax: - - rt merge <from-id> <to-id> - - Merges the two specified tickets. - --- - -Title: link -Text: - - Syntax: - - rt link [-d] <id-A> <relationship> <id-B> - - Creates (or, with -d, deletes) a link between the specified tickets. - The relationship can (irrespective of case) be any of: - - DependsOn/DependedOnBy: A depends upon B (or vice versa). - RefersTo/ReferredToBy: A refers to B (or vice versa). - MemberOf/HasMember: A is a member of B (or vice versa). - - To view a ticket's relationships, use "rt show ticket/3/links". (See - "rt help ticket" and "rt help show".) - - Options: - - -d Deletes the specified link. - - Examples: - - rt link 2 dependson 3 - rt link -d 4 referredtoby 6 # 6 no longer refers to 4 - --- - -Title: grant -Title: revoke -Text: - --- - -Title: query -Text: - - RT3 uses an SQL-like syntax to specify object selection constraints. - See the <RT:...> documentation for details. - - (XXX: I'm going to have to write it, aren't I?) - --- - -Title: form -Title: forms -Text: - - This program uses RFC822 header-style forms to represent object data - in a form that's suitable for processing both by humans and scripts. - - A form is a set of (field, value) specifications, with some initial - commented text and interspersed blank lines allowed for convenience. - Field names may appear more than once in a form; a comma-separated - list of multiple field values may also be specified directly. - - Field values can be wrapped as in RFC822, with leading whitespace. - The longest sequence of leading whitespace common to all the lines - is removed (preserving further indentation). There is no limit on - the length of a value. - - Multiple forms are separated by a line containing only "--\n". - - (XXX: A more detailed specification will be provided soon. For now, - the server-side syntax checking will suffice.) - --- - -Title: topics -Text: - - Use "rt help <topic>" for help on any of the following subjects: - - - tickets, users, groups, queues. - - show, edit, ls/list/search, new/create. - - - query (search query syntax) - - forms (form specification) - - - objects (how to specify objects) - - types (a list of object types) - - actions/commands (a list of actions) - - usage/syntax (syntax details) - - conf/config/configuration (configuration details) - - examples (a few useful examples) - --- - -Title: example -Title: examples -Text: - - This section will be filled in with useful examples, once it becomes - more clear what examples may be useful. - - For the moment, please consult examples provided with each action. - --- diff --git a/rt/config.layout b/rt/config.layout.in index 23a7775d6..a08f48948 100644 --- a/rt/config.layout +++ b/rt/config.layout.in @@ -102,3 +102,26 @@ customlexdir: ${customdir}/po customlibdir: ${customdir}/lib </Layout> + +<Layout Freeside> + prefix: /opt/rt3 + exec_prefix: ${prefix} + bindir: ${exec_prefix}/bin + sbindir: ${exec_prefix}/sbin + sysconfdir: ${prefix}/etc + mandir: ${prefix}/man + libdir: ${prefix}/lib + datadir: ${prefix}/share + htmldir: %%%FREESIDE_DOCUMENT_ROOT%%%/rt + manualdir: ${datadir}/doc + localstatedir: ${prefix}/var + logfiledir: ${localstatedir}/log + masonstatedir: %%%MASONDATA%%% + sessionstatedir: ${localstatedir}/session_data + customdir: ${prefix}/local + custometcdir: ${customdir}/etc + customhtmldir: ${customdir}/html + customlexdir: ${customdir}/po + customlibdir: ${customdir}/lib +</Layout> + diff --git a/rt/etc/RT_SiteConfig.pm b/rt/etc/RT_SiteConfig.pm index 0afc6045c..572a2baf6 100644 --- a/rt/etc/RT_SiteConfig.pm +++ b/rt/etc/RT_SiteConfig.pm @@ -1 +1,13 @@ +$RT::rtname = '%%%RT_DOMAIN%%%'; +$RT::Organization = '%%%RT_DOMAIN%%%'; + +$RT::Timezone = '%%%RT_TIMEZONE%%%'; + +$RT::WebBaseURL = ''; +$RT::WebPath = '/freeside/rt'; + +$RT::WebExternalAuth = 1; +$RT::WebFallbackToInternal = 1; #no +$RT::WebExternalAuto = 1; + 1; diff --git a/rt/etc/schema.Oracle b/rt/etc/schema.Oracle deleted file mode 100644 index 95cfda2fd..000000000 --- a/rt/etc/schema.Oracle +++ /dev/null @@ -1,354 +0,0 @@ -CREATE SEQUENCE ATTACHMENTS_seq; -CREATE TABLE Attachments ( - id NUMBER(11,0) - CONSTRAINT Attachments_Key PRIMARY KEY, - TransactionId NUMBER(11,0) NOT NULL, - Parent NUMBER(11,0) DEFAULT 0 NOT NULL, - MessageId VARCHAR2(160), - Subject VARCHAR2(255), - Filename VARCHAR2(255), - ContentType VARCHAR2(80), - ContentEncoding VARCHAR2(80), - Content CLOB, - Headers CLOB, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); -CREATE INDEX Attachments2 ON Attachments (TransactionId); -CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId); - - -CREATE SEQUENCE QUEUES_seq; -CREATE TABLE Queues ( - id NUMBER(11,0) - CONSTRAINT Queues_Key PRIMARY KEY, - Name VARCHAR2(200) CONSTRAINT Queues_Name_Unique UNIQUE NOT NULL, - Description VARCHAR2(255), - CorrespondAddress VARCHAR2(120), - CommentAddress VARCHAR2(120), - InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - DefaultDueIn NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); - CREATE INDEX Queues1 ON Queues (lower(Name)); -CREATE INDEX Queues2 ON Queues (Disabled); - - -CREATE SEQUENCE LINKS_seq; -CREATE TABLE Links ( - id NUMBER(11,0) - CONSTRAINT Links_Key PRIMARY KEY, - Base VARCHAR2(240), - Target VARCHAR2(240), - Type VARCHAR2(20) NOT NULL, - LocalTarget NUMBER(11,0) DEFAULT 0 NOT NULL, - LocalBase NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); -CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type); -CREATE INDEX Links2 ON Links (Base, Type); -CREATE INDEX Links3 ON Links (Target, Type); -CREATE INDEX Links4 ON Links(Type,LocalBase); - - -CREATE SEQUENCE PRINCIPALS_seq; -CREATE TABLE Principals ( - id NUMBER(11,0) - CONSTRAINT Principals_Key PRIMARY KEY, - PrincipalType VARCHAR2(16), - ObjectId NUMBER(11,0), - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE UNIQUE INDEX Principals2 ON Principals (ObjectId); - - -CREATE SEQUENCE GROUPS_seq; -CREATE TABLE Groups ( - id NUMBER(11,0) - CONSTRAINT Groups_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - Domain VARCHAR2(64), - Type VARCHAR2(64), - Instance NUMBER(11,0) DEFAULT 0 -- NOT NULL --- Instance VARCHAR2(64) -); -CREATE INDEX Groups1 ON Groups (lower( Domain), Instance, lower(Type), id); -CREATE INDEX Groups2 ON Groups (lower(Type), Instance, lower(Domain)); - - -CREATE SEQUENCE SCRIPCONDITIONS_seq; -CREATE TABLE ScripConditions ( - id NUMBER(11, 0) - CONSTRAINT ScripConditions_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - ExecModule VARCHAR2(60), - Argument VARCHAR2(255), - ApplicableTransTypes VARCHAR2(60), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - - -CREATE SEQUENCE TRANSACTIONS_seq; -CREATE TABLE Transactions ( - id NUMBER(11,0) - CONSTRAINT Transactions_Key PRIMARY KEY, - EffectiveTicket NUMBER(11,0) DEFAULT 0 NOT NULL, - Ticket NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeTaken NUMBER(11,0) DEFAULT 0 NOT NULL, - Type VARCHAR2(20), - Field VARCHAR2(40), - OldValue VARCHAR2(255), - NewValue VARCHAR2(255), - Data VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); -CREATE INDEX Transactions1 ON Transactions (Ticket); -CREATE INDEX Transactions2 ON Transactions (EffectiveTicket); - - -CREATE SEQUENCE SCRIPS_seq; -CREATE TABLE Scrips ( - id NUMBER(11,0) - CONSTRAINT Scrips_Key PRIMARY KEY, - Description VARCHAR2(255), - ScripCondition NUMBER(11,0) DEFAULT 0 NOT NULL, - ScripAction NUMBER(11,0) DEFAULT 0 NOT NULL, - ConditionRules CLOB, - ActionRules CLOB, - CustomIsApplicableCode CLOB, - CustomPrepareCode CLOB, - CustomCommitCode CLOB, - Stage VARCHAR2(32), - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Template NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - - -CREATE SEQUENCE ACL_seq; -CREATE TABLE ACL ( - id NUMBER(11,0) - CONSTRAINT ACL_Key PRIMARY KEY, - PrincipalType VARCHAR2(25) NOT NULL, - PrincipalId NUMBER(11,0) NOT NULL, - RightName VARCHAR2(25) NOT NULL, - ObjectType VARCHAR2(25) NOT NULL, - ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, - DelegatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - DelegatedFrom NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX ACL1 ON ACL(RightName, ObjectType, ObjectId, PrincipalType, PrincipalId); - - -CREATE SEQUENCE GROUPMEMBERS_seq; -CREATE TABLE GroupMembers ( - id NUMBER(11,0) - CONSTRAINT GroupMembers_Key PRIMARY KEY, - GroupId NUMBER(11,0) DEFAULT 0 NOT NULL, - MemberId NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers (GroupId, MemberId); - - -CREATE SEQUENCE CachedGroupMembers_seq; -CREATE TABLE CachedGroupMembers ( - id NUMBER(11,0) - CONSTRAINT CachedGroupMembers_Key PRIMARY KEY, - GroupId NUMBER(11,0), - MemberId NUMBER(11,0), - Via NUMBER(11,0), - ImmediateParentId NUMBER(11,0), - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX DisGrouMem ON CachedGroupMembers (GroupId, MemberId, Disabled); -CREATE INDEX GrouMem ON CachedGroupMembers (GroupId, MemberId); - - -CREATE SEQUENCE USERS_seq; -CREATE TABLE Users ( - id NUMBER(11,0) - CONSTRAINT Users_Key PRIMARY KEY, - Name VARCHAR2(200) CONSTRAINT Users_Name_Unique - unique NOT NULL, - Password VARCHAR2(40), - Comments CLOB, - Signature CLOB, - EmailAddress VARCHAR2(120), - FreeFormContactInfo CLOB, - Organization VARCHAR2(200), - RealName VARCHAR2(120), - NickName VARCHAR2(16), - Lang VARCHAR2(16), - EmailEncoding VARCHAR2(16), - WebEncoding VARCHAR2(16), - ExternalContactInfoId VARCHAR2(100), - ContactInfoSystem VARCHAR2(30), - ExternalAuthId VARCHAR2(100), - AuthSystem VARCHAR2(30), - Gecos VARCHAR2(16), - HomePhone VARCHAR2(30), - WorkPhone VARCHAR2(30), - MobilePhone VARCHAR2(30), - PagerPhone VARCHAR2(30), - Address1 VARCHAR2(200), - Address2 VARCHAR2(200), - City VARCHAR2(100), - State VARCHAR2(100), - Zip VARCHAR2(16), - Country VARCHAR2(50), - Timezone VARCHAR2(50), - PGPKey CLOB, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); --- CREATE UNIQUE INDEX Users1 ON Users (Name); - -CREATE INDEX Users2 ON Users( LOWER(name)); -CREATE INDEX Users4 ON Users (lower(EmailAddress)); - - -CREATE SEQUENCE TICKETS_seq; -CREATE TABLE Tickets ( - id NUMBER(11, 0) - CONSTRAINT Tickets_Key PRIMARY KEY, - EffectiveId NUMBER(11,0) DEFAULT 0 NOT NULL, - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Type VARCHAR2(16), - IssueStatement NUMBER(11,0) DEFAULT 0 NOT NULL, - Resolution NUMBER(11,0) DEFAULT 0 NOT NULL, - Owner NUMBER(11,0) DEFAULT 0 NOT NULL, - Subject VARCHAR2(200) DEFAULT '[no subject]', - InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - Priority NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeEstimated NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeWorked NUMBER(11,0) DEFAULT 0 NOT NULL, - Status VARCHAR2(10), - TimeLeft NUMBER(11,0) DEFAULT 0 NOT NULL, - Told DATE, - Starts DATE, - Started DATE, - Due DATE, - Resolved DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX Tickets1 ON Tickets (Queue, Status); -CREATE INDEX Tickets2 ON Tickets (Owner); -CREATE INDEX Tickets4 ON Tickets (id, Status); -CREATE INDEX Tickets5 ON Tickets (id, EffectiveId); -CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type); - - -CREATE SEQUENCE SCRIPACTIONS_seq; -CREATE TABLE ScripActions ( - id NUMBER(11,0) - CONSTRAINT ScripActions_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - ExecModule VARCHAR2(60), - Argument VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - - -CREATE SEQUENCE TEMPLATES_seq; -CREATE TABLE Templates ( - id NUMBER(11,0) - CONSTRAINT Templates_Key PRIMARY KEY, - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Name VARCHAR2(200) NOT NULL, - Description VARCHAR2(255), - Type VARCHAR2(16), - Language VARCHAR2(16), - TranslationOf NUMBER(11,0) DEFAULT 0 NOT NULL, - Content CLOB, - LastUpdated DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); - - -CREATE SEQUENCE TICKETCUSTOMFIELDVALUES_seq; -CREATE TABLE TicketCustomFieldValues ( - id NUMBER(11,0) - CONSTRAINT TicketCustomFieldValues_Key PRIMARY KEY, - Ticket NUMBER(11,0), - CustomField NUMBER(11,0) NOT NULL, - Content VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - -CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content); -CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket); - -CREATE SEQUENCE CUSTOMFIELDS_seq; -CREATE TABLE CustomFields ( - id NUMBER(11,0) - CONSTRAINT CustomFields_Key PRIMARY KEY, - Name VARCHAR2(200), - Type VARCHAR2(200), - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Description VARCHAR2(255), - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX CustomFields1 ON CustomFields (Disabled, Queue); - - -CREATE SEQUENCE CUSTOMFIELDVALUES_seq; -CREATE TABLE CustomFieldValues ( - id NUMBER(11,0) - CONSTRAINT CustomFieldValues_Key PRIMARY KEY, - CustomField NUMBER(11,0), - Name VARCHAR2(200), - Description VARCHAR2(255), - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - -CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField); - -CREATE TABLE sessions ( - id VARCHAR2(32) - CONSTRAINT Sessions_Key PRIMARY KEY, - a_session CLOB, - LastUpdated DATE -); - diff --git a/rt/html/Elements/Footer b/rt/html/Elements/Footer index 5c833f886..052bf87e9 100644 --- a/rt/html/Elements/Footer +++ b/rt/html/Elements/Footer @@ -28,9 +28,11 @@ <td> % } <& /Elements/Callback, %ARGS &> +<!-- <div class="bpscredits"> »|« <&|/l, $RT::VERSION &>RT [_1] from <a href="http://bestpractical.com">Best Practical Solutions, LLC</a>.</&> </div> +--> % if ($Debug) { <HR> <b><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></b> diff --git a/rt/html/Elements/Header b/rt/html/Elements/Header index 23ab5f781..38cb4bd6b 100644 --- a/rt/html/Elements/Header +++ b/rt/html/Elements/Header @@ -42,10 +42,9 @@ ONLOAD=" > <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#FFFFFF"> <tr> - <td colspan=2><a href="http://bestpractical.com"><img src="<%$RT::WebImagesURL%>/bplogo.gif" alt="" width="230" height="50"></a></td> - <td> </td> - <td> </td> - <td width="50%" align="right"> + <td rowspan=2><img border=0 alt="freeside" src="<%$RT::WebImagesURL%>/small-logo.png" width="92" height="62"></td> + <td align="left" rowspan=2><font size=6><% FS::Conf->new->config('company_name') %> Ticketing</font></td> + <td align="right" valign="top"> % if ($session{'CurrentUser'} && $session{'CurrentUser'}->Id && $LoggedIn) { <SPAN STYLE="display: none"><A HREF="#skipnav"><&|/l&>Skip Menu</&></A> |</SPAN> <A HREF="<%$RT::WebPath%><% $Prefs %>" ><&|/l&>Preferences</&></A> @@ -59,6 +58,31 @@ ONLOAD=" <&|/l&>Not logged in.</&> % } </td> + + </tr> + <tr> + + <td align=right valign=bottom> + <table> + <tr> + <td align=right> + <FONT SIZE="-3"> + <A HREF="http://www.sisd.com/freeside">Freeside</A> v<% $FS::VERSION %><BR> + <A HREF="docs/">Documentation</A><BR> + </FONT> + </td> + <td bgcolor=#000000></td> + <td align=left> + <FONT SIZE="-3"> + <A HREF="http://www.bestpractical.com/rt">RT</A> v<% $RT::VERSION %><BR> + <A HREF="http://wiki.bestpractical.com/">Documentation</A><BR> + </FONT> + </td> + + </tr> + </table> + </td> + </tr> </table> <%INIT> diff --git a/rt/html/Elements/PageLayout b/rt/html/Elements/PageLayout index 685317581..6146b807c 100644 --- a/rt/html/Elements/PageLayout +++ b/rt/html/Elements/PageLayout @@ -21,12 +21,12 @@ %# %# %# END LICENSE BLOCK -<table class="darkblue" border=0 cellspacing=0 cellpadding=0 width="100%"> - <th class="titlebox" align="left"><span class="rtname"><%$AppName%></span> +<table class="lightgray" border=0 cellspacing=0 cellpadding=0 width="100%"> + <th class="lightgray" align="left" width=50%><span class="rtname"><%$AppName%></span> </th> <span class="topactions"> % foreach my $action (sort keys %{$topactions}) { - <td class="darkblueright"> + <td class="lightgrayright"> <%$topactions->{"$action"}->{'html'} |n %> </td> % } @@ -35,18 +35,18 @@ <table border=0 cellspacing=0 cellpadding=0 width="100%" height="100%"> %# Vertical menu <TR height="100%"> -<TD valign="top" width="140" class="blue"> +<TD valign="top" width="140" class="lightgray"> <& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &> </TD> <td valign="top"> <table width="100%" height="100%" border="0" cellpadding="0" cellspacing="0"> <tr> - <td class="blue" valign="top"> + <td class="mediumgray" valign="top"> <span class="title"><%$title%></span> </td> </tr> <tr> -<td class="blueright" valign="top"> +<td class="mediumgrayright" valign="top"> <span class="nav"> % if ($actions) { % my @actions; @@ -57,7 +57,8 @@ % push @actions, "<A class='nav' HREF=\"".$RT::WebPath."/".$actions->{$action}->{'path'}."\">".$actions->{$action}->{'title'}."</A>"; % } % } -<% join(" | ", @actions) | n %> +%#<% join(" | ", @actions) | n %> +<% '['. join("] [", @actions). ']' | n %> % if ($subactions) { % my @actions; % foreach my $action (sort keys %{$subactions}) { @@ -95,5 +96,5 @@ $tabs => undef $actions => undef $subactions => undef $title => $m->callers(-1)->path -$AppName => undef +$AppName => '' </%ARGS> diff --git a/rt/html/Elements/SimpleSearch b/rt/html/Elements/SimpleSearch index 4a0d10656..85f37403f 100644 --- a/rt/html/Elements/SimpleSearch +++ b/rt/html/Elements/SimpleSearch @@ -23,5 +23,5 @@ %# END LICENSE BLOCK <form action="<% $RT::WebPath %>/index.html"> <input size="12" name="q" autocomplete="off" accesskey="0"> -<input type="submit" value="<&|/l&>Search</&>"> +<input type="submit" value="<&|/l&>Search tickets</&>"> </form> diff --git a/rt/html/Elements/Tabs b/rt/html/Elements/Tabs index 4db3849c6..6791e5062 100644 --- a/rt/html/Elements/Tabs +++ b/rt/html/Elements/Tabs @@ -40,7 +40,12 @@ my $basetopactions = { B => { html => $m->scomp('/Elements/SimpleSearch') } }; -my $basetabs = { A => { title => loc('Homepage'), +my $basetabs = { + ' A'=> { title => 'Billing Main', + path => '..', + }, + A => { #title => loc('Homepage'), + title => 'Ticketing Main', path => '', }, B => { title => loc('Tickets'), diff --git a/rt/html/NoAuth/images/small-logo.png b/rt/html/NoAuth/images/small-logo.png Binary files differnew file mode 100644 index 000000000..1e415e6d8 --- /dev/null +++ b/rt/html/NoAuth/images/small-logo.png diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index 159e79cd6..3e8e3f928 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -23,15 +23,18 @@ %# END LICENSE BLOCK SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; - color: #FFFFFF; +%# color: #FFFFFF; + color: #000000; text-decoration: none; white-space: nowrap} .nav2 { font-size: 10px; white-space: nowrap} .nav { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 13px; - font-weight: normal; - color: #FFFFFF; +%# font-weight: normal; + font-weight: bold; +%# color: #FFFFFF; + color: #000000; text-decoration: none; white-space: nowrap} .currentnav { font-family: Verdana, Arial, Helvetica, sans-serif; @@ -43,13 +46,15 @@ SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; .topnav { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; font-weight: normal; - color: #FFFFFF; +%# color: #FFFFFF; + color: #000000; text-decoration: none; white-space: nowrap} .currenttopnav { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; font-weight: bold; - color: #FFFF66; +%# color: #FFFF66; + color: #000000; background-color: #cccccc; text-decoration: none; white-space: nowrap} .topactions { font-family: Verdana, Arial, Helvetica, sans-serif; @@ -79,12 +84,16 @@ SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; vertical-align: top; text-align: left; } -.blue { background-color: #4682B4; +.blue { + background-color: #4682B4; +%# background-color: #eeeeee; background-position: left top; vertical-align: top; text-align: left; } -.blueright { background-color: #4682B4; +.blueright { + background-color: #4682B4; +%# background-color: #eeeeee; background-position: left top; vertical-align: top; text-align: right; @@ -94,12 +103,50 @@ SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; vertical-align: top; text-align: left; } -.darkblue { background-color: #000080; +.darkblue { + background-color: #000080; + background-position: left top; + vertical-align: top; + text-align: left; + } +.darkblueright { + background-color: #000080; + background-position: left top; + vertical-align: top; + text-align: right; + } +.lightgray { + background-color: #eeeeee; + background-position: left top; + vertical-align: top; + text-align: left; + } +.lightgrayright { + background-color: #eeeeee; + background-position: left top; + vertical-align: top; + text-align: right; + } +.mediumgray { + background-color: #cccccc; + background-position: left top; + vertical-align: top; + text-align: left; + } +.mediumgrayright { + background-color: #cccccc; + background-position: left top; + vertical-align: top; + text-align: right; + } +.white { + background-color: #ffffff; background-position: left top; vertical-align: top; text-align: left; } -.darkblueright { background-color: #000080; +.whiteright { + background-color: #ffffff; background-position: left top; vertical-align: top; text-align: right; @@ -266,7 +313,9 @@ SPAN.date { font-size: 0.8em } span.title { font-size: 1.6em; vertical-align: middle; - color: #ffffff;} +%# color: #ffffff; + color: #000000; + } span.productname { font-size: 2em; color: #0066cc;} SPAN.titleboxtitle { diff --git a/rt/html/Ticket/Elements/AddCustomers b/rt/html/Ticket/Elements/AddCustomers new file mode 100644 index 000000000..66480e2f1 --- /dev/null +++ b/rt/html/Ticket/Elements/AddCustomers @@ -0,0 +1,53 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# +%# 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. +<BR> +<%$msg%><br> + +% if (@Customers) { + +<br><i>(Check box to link)<i> +<table> +% foreach my $customer (@Customers) { +<tr> + <td> + <input type="checkbox" name="Ticket-AddCustomer-<% $customer->custnum %>" VALUE="1" <% scalar(@Customers) == 1 ? 'CHECKED' : '' %>> +%# <% $customer->name %> + <A HREF="<% $p %>view/cust_main.cgi?<% $customer->custnum %>"><% small_custview( $customer, scalar(FS::Conf->new->config('countrydefault')), 1 ) |n %> + </td> +</tr> +% } + +% } + +<%INIT> +my ($msg); + +my @Customers = (); +if ( $CustomerString ) { + @Customers = smart_search( 'search' => $CustomerString ); + warn scalar(@Customers); +} + +my @Services = (); +if ($ServiceString) { + @Services = (); #service_search(); +} + +eval { use FS::CGI qw( popurl small_custview ); }; +my $p = eval { popurl(3); }; + +</%INIT> + +<%ARGS> +$CustomerString => undef +$ServiceString => undef +</%ARGS> diff --git a/rt/html/Ticket/Elements/EditCustomers b/rt/html/Ticket/Elements/EditCustomers new file mode 100644 index 000000000..10b9c5baf --- /dev/null +++ b/rt/html/Ticket/Elements/EditCustomers @@ -0,0 +1,67 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# +%# 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. +<TABLE width=100%> + <TR> + <TD VALIGN=TOP WIDTH=50%> + <h3><&|/l&>Current Customers</&></h3> + +<table> + <tr> + <td><i><&|/l&>(Check box to disassociate)</&></i></td> + </tr> + <tr> + <td class="value"> +% #while (my $link = $Ticket->MemberOf->Next) { +% foreach my $link ( +% grep { $_->TargetURI->Resolver->{'fstable'} eq 'cust_main' } +% grep { $_->TargetURI->Scheme eq 'freeside' } +% @{ $Ticket->_Links('Base')->ItemsArrayRef } +% ) { + + <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>"> +%# <& ShowLink, URI => $link->TargetURI &><br> + <A HREF="<% $link->TargetURI->Resolver->HREF %>"><% $link->TargetURI->Resolver->AsStringLong |n %> + <BR> +% } + </td> + </tr> +</table> + +</TD> + +<TD VALIGN=TOP> +<h3><&|/l&>New Customer Links</&></h3> +<&|/l&>Find customer</&><BR> +<input name="CustomerString"> +<input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>"> +<br><i>cust #, last name, or company</i> +<BR> +%#<BR> +%#<&|/l&>Find service</&><BR> +%#<input name="ServiceString"> +%#<input type=submit name="OnlySearchForServices" value="<&|/l&>Go!</&>"> +%#<br><i>username, username@domain, domain, or IP address</i> +%#<BR> + +<& AddCustomers, Ticket => $Ticket, + CustomerString => $CustomerString, + ServiceString => $ServiceString, &> + +</TD> +</TR> +</TABLE> + +<%ARGS> +$CustomerString => undef +$ServiceString => undef +$Ticket => undef +</%ARGS> diff --git a/rt/html/Ticket/Elements/ShowCustomers b/rt/html/Ticket/Elements/ShowCustomers new file mode 100644 index 000000000..5519d24cf --- /dev/null +++ b/rt/html/Ticket/Elements/ShowCustomers @@ -0,0 +1,40 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# +%# 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. +<table> +% my $cust = 0; +% foreach my $customerURI ( +% grep { $_->Resolver->{'fstable'} eq 'cust_main' } +% grep { $_->Scheme eq 'freeside' } +% map { $_->TargetURI } +% @{ $Ticket->_Links('Base')->ItemsArrayRef } +% ) { +% $cust++; +% my $cust_main = ''; + <tr> + <td class="value"> + <A HREF="<% $customerURI->Resolver->HREF %>"><% $customerURI->Resolver->AsStringLong |n %> + </td> + </tr> +% } +% unless ( $cust ) { + <tr> + <td class="labeltop"> + <i>(none)<i> + </td> + </tr> + +% } +</table> +<%ARGS> +$Ticket => undef +</%ARGS> + diff --git a/rt/html/Ticket/Elements/ShowSummary b/rt/html/Ticket/Elements/ShowSummary index 5bcc94b41..1b9f62908 100644 --- a/rt/html/Ticket/Elements/ShowSummary +++ b/rt/html/Ticket/Elements/ShowSummary @@ -47,6 +47,15 @@ color => "#333399" &> <& /Ticket/Elements/ShowPeople, Ticket => $Ticket &> <& /Elements/TitleBoxEnd &> + <br> + + <& /Elements/TitleBoxStart, title => loc('Customers'), + title_href =>"$RT::WebPath/Ticket/ModifyCustomers.html?id=".$Ticket->Id, + title_class=> 'inverse', + color => "#7f007b" &> + <& /Ticket/Elements/ShowCustomers, Ticket => $Ticket &> + <& /Elements/TitleBoxEnd &> + <BR> </TD> <TD VALIGN=TOP WIDTH="50%"> diff --git a/rt/html/Ticket/Elements/Tabs b/rt/html/Ticket/Elements/Tabs index cba45df91..26b252707 100644 --- a/rt/html/Ticket/Elements/Tabs +++ b/rt/html/Ticket/Elements/Tabs @@ -101,6 +101,8 @@ my $ticket_page_tabs = { { title => loc('People'), path => "Ticket/ModifyPeople.html?id=" . $id, }, _E => { title => loc('Links'), path => "Ticket/ModifyLinks.html?id=" . $id, }, + _Eb=> { title => loc('Customers'), + path => "Ticket/ModifyCustomers.html?id=" . $id, }, _F => { title => loc('Jumbo'), path => "Ticket/ModifyAll.html?id=" . $id, seperator => 1 diff --git a/rt/html/Ticket/ModifyCustomers.html b/rt/html/Ticket/ModifyCustomers.html new file mode 100644 index 000000000..72d103b23 --- /dev/null +++ b/rt/html/Ticket/ModifyCustomers.html @@ -0,0 +1,49 @@ +%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +%# +%# 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. +<& /Elements/Header, Title => loc("Customers for ticket #[_1]", $Ticket->Id) &> +<& /Ticket/Elements/Tabs, + Ticket => $Ticket, + current_tab => "Ticket/ModifyCustomers.html?id=".$Ticket->Id, + Title => loc("Customers for ticket #[_1]", $Ticket->Id) &> + +<& /Elements/ListActions, actions => \@results &> + +<form action="ModifyCustomers.html" method="post"> +<input type="hidden" name="id" value="<%$Ticket->id%>"> + +<& /Elements/TitleBoxStart, title => loc('Edit Customer Links'), color => "#7f007b"&> +<& Elements/EditCustomers, Ticket => $Ticket, CustomerString => $CustomerString, ServiceString => $ServiceString &> +<& /Elements/TitleBoxEnd &> +<& /Elements/Submit, color => "#7f007b", Label => loc('Save Changes') &> +</form> + + +<%INIT> + +my @results = (); +my $Ticket = LoadTicket($id); + +# if we're trying to search for customers/services and nothing else +unless ( $OnlySearchForCustomers || $OnlySearchForServices) { + @results = ProcessTicketCustomers( TicketObj => $Ticket, ARGSRef => \%ARGS); +} + +</%INIT> + + +<%ARGS> +$OnlySearchForCustomers => undef +$OnlySearchForServices => undef +$CustomerString => undef +$ServiceString => undef +$id => undef +</%ARGS> diff --git a/rt/lib/RT/Interface/Web_Vendor.pm b/rt/lib/RT/Interface/Web_Vendor.pm new file mode 100644 index 000000000..5be20e6b9 --- /dev/null +++ b/rt/lib/RT/Interface/Web_Vendor.pm @@ -0,0 +1,95 @@ +# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am> +# +# 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. + +=head1 NAME + +RT::Interface::Web_Vendor + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +Freeside vendor overlay for RT::Interface::Web. + +=begin testing + +use_ok(RT::Interface::Web_Vendor); + +=end testing + +=cut + +#package RT::Interface::Web; +#use strict; + +package HTML::Mason::Commands; +use strict; + +=head2 ProcessTicketCustomers + +=cut + +sub ProcessTicketCustomers { + my %args = ( + TicketObj => undef, + ARGSRef => undef, + @_ + ); + my @results = (); + + my $Ticket = $args{'TicketObj'}; + my $ARGSRef = $args{'ARGSRef'}; + + ### false laziness w/RT::Interface::Web::ProcessTicketLinks + # Delete links that are gone gone gone. + foreach my $arg ( keys %$ARGSRef ) { + if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) { + my $base = $1; + my $type = $2; + my $target = $3; + + push @results, + "Trying to delete: Base: $base Target: $target Type $type"; + my ( $val, $msg ) = $Ticket->DeleteLink( Base => $base, + Type => $type, + Target => $target ); + + push @results, $msg; + + } + + } + ### + + my @delete_custnums = + map { /^Ticket-AddCustomer-(\d+)$/; $1 } + grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} } + keys %$ARGSRef; + + my @custnums = map { /^Ticket-AddCustomer-(\d+)$/; $1 } + grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} } + keys %$ARGSRef; + + foreach my $custnum ( @custnums ) { + my( $val, $msg ) = + $Ticket->AddLink( 'Type' => 'MemberOf', + 'Target' => "freeside://freeside/cust_main/$custnum", + ); + push @results, $msg; + } + + return @results; + +} + +1; + diff --git a/rt/lib/RT/URI/freeside.pm b/rt/lib/RT/URI/freeside.pm new file mode 100644 index 000000000..ebd24ad60 --- /dev/null +++ b/rt/lib/RT/URI/freeside.pm @@ -0,0 +1,260 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 2004 Kristian Hoffmann <khoff@fire2wire.com> +# Based on the original RT::URI::base and RT::URI::fsck_com_rt. +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK +package RT::URI::freeside; + +use RT::URI::base; +use strict; +use vars qw(@ISA); + +@ISA = qw/RT::URI::base/; + + +=head1 NAME + +RT::URI::base + +=head1 DESCRIPTION + +URI handler for freeside URIs. See http://www.sisd.com/freeside/ for +more information on freeside. + +=cut + + +sub FreesideURIPrefix { + + my $self = shift; + return($self->Scheme . '://freeside'); + +} + +sub FreesideURILabel { + + my $self = shift; + + return(undef) unless (exists($self->{'fstable'}) and + exists($self->{'fspkey'})); + + my $label; + my ($table, $pkey) = ($self->{'fstable'}, $self->{'fspkey'}); + + eval { + use FS::UID qw(dbh); + use FS::Record qw(qsearchs qsearch dbdef); + eval "use FS::$table;"; + use FS::cust_svc; + + my $dbdef = dbdef or die "No dbdef"; + my $pkeyfield = $dbdef->table($table)->primary_key + or die "No primary key for table $table"; + + my $rec = qsearchs($table, { $pkeyfield => $pkey }) + or die "Record with $pkeyfield == $pkey does not exist in table $table"; + + if ($table =~ /^svc_/) { + if ($rec->can('cust_svc')) { + my $cust_svc = $rec->cust_svc or die '$rec->cust_svc failed'; + my ($svc, $tag, $svcdb) = $cust_svc->label; + $label = "Freeside service ${svc}: ${tag}"; + } + } elsif ($table eq 'cust_main') { + #my ($last, $first, $company) = map { $rec->getfield($_) } + # qw(last first company); + #$label = "Freeside customer ${last}, ${first}"; + #$label .= ($company ne '') ? " with ${company}" : ''; + $label = "$pkey: ". $rec->name; + } else { + $label = "Freeside ${table}, ${pkeyfield} == ${pkey}"; + } + + #... other cases + + }; + + if ($label and !$@) { + return($label); + } else { + return(undef); + } + + +} + +sub FreesideURILabelLong { + + my $self = shift; + + return(undef) unless (exists($self->{'fstable'}) and + exists($self->{'fspkey'})); + + my $label; + my ($table, $pkey) = ($self->{'fstable'}, $self->{'fspkey'}); + + eval { + use FS::UID qw(dbh); + use FS::Record qw(qsearchs qsearch dbdef); + eval "use FS::$table;"; + use FS::cust_svc; + + my $dbdef = dbdef or die "No dbdef"; + my $pkeyfield = $dbdef->table($table)->primary_key + or die "No primary key for table $table"; + + my $rec = qsearchs($table, { $pkeyfield => $pkey }) + or die "Record with $pkeyfield == $pkey does not exist in table $table"; + + if ($table =~ /^svc_/) { + #if ($rec->can('cust_svc')) { + # my $cust_svc = $rec->cust_svc or die '$rec->cust_svc failed'; + # my ($svc, $tag, $svcdb) = $cust_svc->label; + # $label = "Freeside service ${svc}: ${tag}"; + #} + $label = ''; + } elsif ($table eq 'cust_main') { + use FS::CGI qw(small_custview); + $label = small_custview( $rec, + scalar(FS::Conf->new->config('countrydefault')), + 1 #nobalance + ); + } else { + #$label = "Freeside ${table}, ${pkeyfield} == ${pkey}"; + $label = ''; + } + + #... other cases + + }; + + if ($label and !$@) { + return($label); + } else { + warn $@; + return(undef); + } + + +} + +sub ParseURI { + my $self = shift; + my $uri = shift; + my ($table, $pkey); + + my $uriprefix = $self->FreesideURIPrefix; + if ($uri =~ /^$uriprefix\/(\w+)\/(\d+)$/) { + $table = $1; + $pkey = $2; + $self->{'scheme'} = $self->Scheme; + } else { + return(undef); + } + + $self->{'uri'} = "${uriprefix}/${table}/${pkey}"; + $self->{'fstable'} = $table; + $self->{'fspkey'} = $pkey; + + my $p; + + eval { + use FS::UID qw(dbh); + use FS::CGI qw(popurl); + + if (dbh) { + $p = popurl(3); + } + + }; + + if ($@ or (!$p)) { + $self->{'href'} = $self->{'uri'}; + } else { + $self->{'href'} = "${p}view/${table}.cgi?${pkey}"; + } + + $self->{'uri'}; + +} + +sub Scheme { + my $self = shift; + return('freeside'); + +} + +sub HREF { + my $self = shift; + return($self->{'href'} || $self->{'uri'}); +} + +sub IsLocal { + my $self = shift; + return undef; +} + +=head2 AsString + +Return a "pretty" string representing the URI object. + +This is meant to be used like this: + + % $re = $uri->Resolver; + <A HREF="<% $re->HREF %>"><% $re->AsString %></A> + +=cut + +sub AsString { + my $self = shift; + my $prettystring; + if ($prettystring = $self->FreesideURILabel) { + return $prettystring; + } else { + return $self->URI; + } +} + +=head2 AsStringLong + +Return a longer (HTML) string representing the URI object. + +=cut + +sub AsStringLong { + my $self = shift; + my $prettystring; + if ($prettystring = $self->FreesideURILabelLong || $self->FreesideURILabel){ + return $prettystring; + } else { + return $self->URI; + } +} + +eval "require RT::URI::base_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Vendor.pm}); +eval "require RT::URI::base_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Local.pm}); + +1; diff --git a/rt/sbin/rt-setup-database b/rt/sbin/rt-setup-database deleted file mode 100644 index 58f882f6e..000000000 --- a/rt/sbin/rt-setup-database +++ /dev/null @@ -1,619 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# -# (Except where explictly superceded by other copyright notices) -# -# 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. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK - -use strict; -use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item); -use vars - qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips); - -use lib "/opt/rt3/lib"; - -#This drags in RT's config.pm -# We do it in a begin block because RT::Handle needs to know the type to do its -# inheritance -use RT; -use Carp; -use RT::User; -use RT::CurrentUser; -use RT::Template; -use RT::ScripAction; -use RT::ACE; -use RT::Group; -use RT::User; -use RT::Queue; -use RT::ScripCondition; -use RT::CustomField; -use RT::Scrip; - -RT::LoadConfig(); -use Term::ReadKey; -use Getopt::Long; - -my %args; - -GetOptions( - \%args, - 'prompt-for-dba-password', 'force', 'debug', - 'action=s', 'dba=s', 'dba-password=s', 'datafile=s', - 'datadir=s' -); - -$| = 1; #unbuffer that output. - -require RT::Handle; -my $Handle = RT::Handle->new($RT::DatabaseType); -$Handle->BuildDSN; -my $dbh; - -if ( $args{'prompt-for-dba-password'} ) { - $args{'dba-password'} = get_dba_password(); - chomp( $args{'dba-password'} ); -} - -unless ( $args{'action'} ) { - help(); - die; -} -if ( $args{'action'} eq 'init' ) { - $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) - || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; - print "Now creating a database for RT.\n"; - if ($RT::DatabaseType ne 'Oracle' || - $args{'dba'} ne $RT::DatabaseUser) { - create_db(); - } else { - print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n"; - } - - $dbh->disconnect; - $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) - || die $DBI::errstr; - - print "Now populating database schema.\n"; - insert_schema(); - print "Now inserting database ACLs\n"; - insert_acl() unless ($RT::DatabaseType eq 'Oracle'); - print "Now inserting RT core system objects\n"; - insert_initial_data(); - print "Now inserting RT data\n"; - insert_data( $RT::EtcPath . "/initialdata" ); -} -elsif ( $args{'action'} eq 'drop' ) { - unless ( $dbh = - DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) ) - { - warn $DBI::errstr; - warn "Database doesn't appear to exist. Aborting database drop."; - exit(0); - } - drop_db(); -} -elsif ( $args{'action'} eq 'insert' ) { - insert_data( $args{'datafile'} ); -} -elsif ($args{'action'} eq 'acl') { - $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) - || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; - insert_acl($args{'datadir'}); -} -elsif ($args{'action'} eq 'schema') { - $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) - || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; - insert_schema($args{'datadir'}); -} - -else { - print STDERR '$0 called with an invalid --action parameter'; - exit(-1); -} - -# {{{ sub insert_schema -sub insert_schema { - my $base_path = (shift || $RT::EtcPath); - my (@schema); - print "Creating database schema.\n"; - - if ( -f $base_path . "/schema." . $RT::DatabaseType ) { - no warnings 'unopened'; - - open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType ); - open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType ); - - my $statement = ""; - foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) { - $line =~ s/\#.*//g; - $line =~ s/--.*//g; - $statement .= $line; - if ( $line =~ /;(\s*)$/ ) { - $statement =~ s/;(\s*)$//g; - push @schema, $statement; - $statement = ""; - } - } - - local $SIG{__WARN__} = sub {}; - my $is_local = 0; # local/etc/schema needs to be nonfatal. - foreach my $statement (@schema) { - if ($statement =~ /^\s*;$/) { $is_local = 1; next; } - print STDERR "SQL: $statement\n" if defined $args{'debug'}; - my $sth = $dbh->prepare($statement) or die $dbh->errstr; - unless ( $sth->execute or $is_local ) { - die "Problem with statement:\n $statement\n" . $sth->errstr; - } - } - - } - else { - die "Couldn't find schema file for " . $RT::DatabaseType . "\n"; - } - print "schema sucessfully inserted\n"; - -} - -# }}} - -# {{{ sub drop_db -sub drop_db { - return if ( $RT::DatabaseType eq 'SQLite' ); - if ( $RT::DatabaseType eq 'Oracle' ) { - print <<END; - -To delete the tables and sequences of the RT Oracle database by running - \@etc/drop.Oracle -through SQLPlus. - -END - return; - } - unless ( $args{'force'} ) { - print <<END; - -About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost. -WARNING: This will erase all data in $RT::DatabaseName. - -END - exit unless _yesno(); - - } - - print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n"; - - $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr; -} - -# }}} - -# {{{ sub create_db -sub create_db { - print "Creating $RT::DatabaseType database $RT::DatabaseName.\n"; - if ( $RT::DatabaseType eq 'SQLite' ) { - return; - } - elsif ( $RT::DatabaseType eq 'Pg' ) { - $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'"); - if ($DBI::errstr) { - $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr; - } - } - elsif ($RT::DatabaseType eq 'Oracle') { - insert_acl(); - } - elsif ( $RT::DatabaseType eq 'Informix' ) { - $ENV{DB_LOCALE} = 'en_us.utf8'; - $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG"); - } - else { - $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr; - } -} - -# }}} - -sub get_dba_password { - print -"In order to create a new database and grant RT access to that database,\n"; - print "this script needs to connect to your " - . $RT::DatabaseType - . " instance on " - . $RT::DatabaseHost . " as " - . $args{'dba'} . ".\n"; - print -"Please specify that user's database password below. If the user has no database\n"; - print "password, just press return.\n\n"; - print "Password: "; - ReadMode('noecho'); - my $password = ReadLine(0); - ReadMode('normal'); - return ($password); -} - -# {{{ sub _yesno -sub _yesno { - print "Proceed [y/N]:"; - my $x = scalar(<STDIN>); - $x =~ /^y/i; -} - -# }}} - -# {{{ insert_acls -sub insert_acl { - - my $base_path = (shift || $RT::EtcPath); - - if ( $RT::DatabaseType =~ /^oracle$/i ) { - do $base_path . "/acl.Oracle" - || die "Couldn't find ACLS for Oracle\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^pg$/i ) { - do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^mysql$/i ) { - do $base_path . "/acl.mysql" - || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^informix$/i ) { - do $base_path . "/acl.Informix" - || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^SQLite$/i ) { - return; - } - else { - die "Unknown RT database type"; - } - - my @acl = acl($dbh); - foreach my $statement (@acl) { - print STDERR $statement if $args{'debug'}; - my $sth = $dbh->prepare($statement) or die $dbh->errstr; - unless ( $sth->execute ) { - die "Problem with statement:\n $statement\n" . $sth->errstr; - } - } -} - -# }}} - -=head2 get_system_dsn - -Returns a dsn suitable for database creates and drops -and user creates and drops - -=cut - -sub get_system_dsn { - - my $dsn = $Handle->DSN; - - #with mysql, you want to connect sans database to funge things - if ( $RT::DatabaseType eq 'mysql' ) { - $dsn =~ s/dbname=$RT::DatabaseName//; - - # with postgres, you want to connect to database1 - } - elsif ( $RT::DatabaseType eq 'Pg' ) { - $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/; - } - elsif ( $RT::DatabaseType eq 'Informix' ) { - # with Informix, you want to connect sans database: - $dsn =~ s/Informix:$RT::DatabaseName/Informix:/; - } - return $dsn; -} - -sub insert_initial_data { - - RT::InitLogging(); - - #connect to the db, for actual RT work - require RT::Handle; - $RT::Handle = RT::Handle->new(); - $RT::Handle->Connect(); - - #Put together a current user object so we can create a User object - my $CurrentUser = new RT::CurrentUser(); - - print "Checking for existing system user ($CurrentUser)..."; - my $test_user = RT::User->new($CurrentUser); - $test_user->Load('RT_System'); - if ( $test_user->id ) { - print "found!\n\nYou appear to have a functional RT database.\n" - . "Exiting, so as not to clobber your existing data.\n"; - exit(-1); - - } - else { - print "not found. This appears to be a new installation.\n"; - } - - print "Creating system user..."; - my $RT_System = new RT::User($CurrentUser); - - my ( $val, $msg ) = $RT_System->_BootstrapCreate( - Name => 'RT_System', - RealName => 'The RT System itself', - Comments => -'Do not delete or modify this user. It is integral to RT\'s internal database structures', - Creator => '1' ); - - unless ($val) { - print "$msg\n"; - exit(1); - } - print "done.\n"; - $RT::Handle->Disconnect(); - -} - -# load some sort of data into the database - -sub insert_data { - my $datafile = shift; - - #Connect to the database and get RT::SystemUser and RT::Nobody loaded - RT::Init; - - my $CurrentUser = RT::CurrentUser->new(); - $CurrentUser->LoadByName('RT_System'); - - if ( $datafile eq $RT::EtcPath . "/initialdata" ) { - - print "Creating Superuser ACL..."; - - my $superuser_ace = RT::ACE->new($CurrentUser); - $superuser_ace->_BootstrapCreate( - PrincipalId => ACLEquivGroupId( $CurrentUser->Id ), - PrincipalType => 'Group', - RightName => 'SuperUser', - ObjectType => 'RT::System', - ObjectId => '1' ); - - } - - # Slurp in stuff to insert from the datafile. Possible things to go in here:- - # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates - - require $datafile - || die "Couldn't find initial data for import\n" . $@; - - if (@Groups) { - print "Creating groups..."; - foreach $item (@Groups) { - my $new_entry = RT::Group->new($CurrentUser); - my ( $return, $msg ) = $new_entry->_Create(%$item); - print "(Error: $msg)" unless ($return); - print $return. "."; - } - print "done.\n"; - } - if (@Users) { - print "Creating users..."; - foreach $item (@Users) { - my $new_entry = new RT::User($CurrentUser); - my ( $return, $msg ) = $new_entry->Create(%$item); - print "(Error: $msg)" unless ($return); - print $return. "."; - } - print "done.\n"; - } - if (@Queues) { - print "Creating queues..."; - for $item (@Queues) { - my $new_entry = new RT::Queue($CurrentUser); - my ( $return, $msg ) = $new_entry->Create(%$item); - print "(Error: $msg)" unless ($return); - print $return. "."; - } - print "done.\n"; - } - if (@ACL) { - print "Creating ACL..."; - for my $item (@ACL) { - - my ($princ, $object); - - # Global rights or Queue rights? - if ($item->{'Queue'}) { - $object = RT::Queue->new($CurrentUser); - $object->Load( $item->{'Queue'} ); - } else { - $object = $RT::System; - } - - # Group rights or user rights? - if ($item->{'GroupDomain'}) { - $princ = RT::Group->new($CurrentUser); - if ($item->{'GroupDomain'} eq 'UserDefined') { - $princ->LoadUserDefinedGroup( $item->{'GroupId'} ); - } elsif ($item->{'GroupDomain'} eq 'SystemInternal') { - $princ->LoadSystemInternalGroup( $item->{'GroupType'} ); - } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' && - $item->{'Queue'}) { - $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'}, - Queue => $object->id); - } else { - $princ->Load( $item->{'GroupId'} ); - } - } else { - $princ = RT::User->new($CurrentUser); - $princ->Load( $item->{'UserId'} ); - } - - # Grant it - my ( $return, $msg ) = $princ->PrincipalObj->GrantRight( - Right => $item->{'Right'}, - Object => $object ); - - if ($return) { - print $return. "."; - } - else { - print $msg . "."; - - } - - } - print "done.\n"; - } - if (@CustomFields) { - print "Creating custom fields..."; - for $item (@CustomFields) { - my $new_entry = new RT::CustomField($CurrentUser); - my $values = $item->{'Values'}; - delete $item->{'Values'}; - my $q = $item->{'Queue'}; - my $q_obj = RT::Queue->new($CurrentUser); - $q_obj->Load($q); - if ( $q_obj->Id ) { - $item->{'Queue'} = $q_obj->Id; - } - elsif ( $q == 0 ) { - $item->{'Queue'} = 0; - } - else { - print "(Error: Could not find queue " . $q . ")\n" - unless ( $q_obj->Id ); - next; - } - my ( $return, $msg ) = $new_entry->Create(%$item); - - foreach my $value ( @{$values} ) { - my ( $eval, $emsg ) = $new_entry->AddValue(%$value); - print "(Error: $emsg)\n" unless ($eval); - } - - print "(Error: $msg)\n" unless ($return); - print $return. "."; - } - - print "done.\n"; - } - - if (@ScripActions) { - print "Creating ScripActions..."; - - for $item (@ScripActions) { - my $new_entry = RT::ScripAction->new($CurrentUser); - my $return = $new_entry->Create(%$item); - print $return. "."; - } - - print "done.\n"; - } - - if (@ScripConditions) { - print "Creating ScripConditions..."; - - for $item (@ScripConditions) { - my $new_entry = RT::ScripCondition->new($CurrentUser); - my $return = $new_entry->Create(%$item); - print $return. "."; - } - - print "done.\n"; - } - - if (@Templates) { - print "Creating templates..."; - - for $item (@Templates) { - my $new_entry = new RT::Template($CurrentUser); - my $return = $new_entry->Create(%$item); - print $return. "."; - } - print "done.\n"; - } - if (@Scrips) { - print "Creating scrips..."; - - for $item (@Scrips) { - my $new_entry = new RT::Scrip($CurrentUser); - my ( $return, $msg ) = $new_entry->Create(%$item); - if ($return) { - print $return. "."; - } - else { - print "(Error: $msg)\n"; - } - } - print "done.\n"; - } - $RT::Handle->Disconnect(); - -} - -=head2 ACLEquivGroupId - -Given a userid, return that user's acl equivalence group - -=cut - -sub ACLEquivGroupId { - my $username = shift; - my $user = RT::User->new($RT::SystemUser); - $user->Load($username); - my $equiv_group = RT::Group->new($RT::SystemUser); - $equiv_group->LoadACLEquivalenceGroup($user); - return ( $equiv_group->Id ); -} - -sub help { - - print <<EOF; - -$0: Set up RT's database - ---action init Initialize the database - drop Drop the database. - This will ERASE ALL YOUR DATA - insert Insert data into RT's database. - By default, will use RT's installation data. - To use a local or supplementary datafile, specify it - using the '--datafile' option below. - - acl Initialize only the database ACLs - To use a local or supplementary datafile, specify it - using the '--datadir' option below. - - schema Initialize only the database schema - To use a local or supplementary datafile, specify it - using the '--datadir' option below. - ---datafile /path/to/datafile ---datadir /path/to/ Used to specify a path to find the local - database schema and acls to be installed. - - ---dba dba's username ---dba-password dba's password ---prompt-for-dba-password Ask for the database administrator's password interactively - - -EOF - -} - -1; diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in index a7ee86d5d..0b9a1071a 100644 --- a/rt/sbin/rt-setup-database.in +++ b/rt/sbin/rt-setup-database.in @@ -110,6 +110,9 @@ elsif ( $args{'action'} eq 'drop' ) { } drop_db(); } +elsif ( $args{'action'} eq 'insert_initial' ) { + insert_initial_data(); +} elsif ( $args{'action'} eq 'insert' ) { insert_data( $args{'datafile'} ); } @@ -591,6 +594,8 @@ $0: Set up RT's database --action init Initialize the database drop Drop the database. This will ERASE ALL YOUR DATA + insert_initial + Insert RT's core system objects insert Insert data into RT's database. By default, will use RT's installation data. To use a local or supplementary datafile, specify it diff --git a/rt/sbin/rt-test-dependencies b/rt/sbin/rt-test-dependencies deleted file mode 100644 index c1591b189..000000000 --- a/rt/sbin/rt-test-dependencies +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/perl -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# -# (Except where explictly superceded by other copyright notices) -# -# 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. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK - -# -# This is just a basic script that checks to make sure that all -# the modules needed by RT before you can install it. -# - -use strict; -no warnings qw(numeric redefine); -use Getopt::Long; -use CPAN; -my %args; -my %deps; -GetOptions(\%args,'install', 'with-MYSQL', 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE', 'with-ORACLE', 'with-FASTCGI', 'with-SPEEDYCGI', 'with-MODPERL1', 'with-MODPERL2' ,'with-DEV'); - -if (!keys %args) { - help(); - exit(0); -} -if ($args{'with-MODPERL2'}) { - warn_modperl2(); -} - -$args{'with-MASON'} = 1; -$args{'with-CORE'} = 1; -$args{'with-DEV'} =1; -$args{'with-CLI'} =1; -$args{'with-MAILGATE'} =1; -if ($] < 5.007) { -$args{'with-I18N-COMPAT'} = 1; -} - -sub warn_modperl2 { - print <<'.'; - NOTE: mod_perl 2.0 isn't quite ready for prime_time just yet; - Best Practical Solutions strongly recommends that sites use - Apache 1.3 or FastCGI. If you MUST use mod_perl 2.0 (or 1.99), - please read the mailing list archives before asking for help. -. - sleep 5; -} - - -sub help { - - print <<'.'; - -By default, testdeps determine whether you have -installed all the perl modules RT needs to run. - - --install Install missing modules - -The following switches will tell the tool to check for specific dependencies - - --with-mysql Database interface for MySQL - --with-postgresql Database interface for PostgreSQL - --with-sqlite Database interface and driver for SQLite (unsupported) - --with-oracle Database interface for oracle (unsupported) - - --with-fastcgi Libraries needed to support the fastcgi handler - --with-speedycgi Libraries needed to support the speedycgi handler - --with-modperl1 Libraries needed to support the modperl 1 handler - --with-modperl2 Libraries needed to support the modperl 2 handler - - --with-dev Tools needed for RT development -. -} - - -sub _ { - map { /(\S+)\s*(\S*)/; $1 => ($2 ? $2 :'') } split ( /\n/, $_[0] ); -} - -$deps{'CORE'} = [ _( << '.') ]; -Digest::MD5 2.27 -DBI 1.37 -Test::Inline -Class::ReturnValue 0.40 -DBIx::SearchBuilder 0.97 -Text::Template -File::Spec 0.8 -HTML::Entities -Net::Domain -Log::Dispatch 2.0 -Locale::Maketext 1.06 -Locale::Maketext::Lexicon 0.32 -Locale::Maketext::Fuzzy -MIME::Entity 5.108 -Mail::Mailer 1.57 -Net::SMTP -Text::Wrapper -Time::ParseDate -File::Temp -Term::ReadKey -Text::Autoformat -Text::Quoted 1.3 -Scalar::Util -. - -$deps{'MASON'} = [ _( << '.') ]; -Params::Validate 0.02 -Cache::Cache -Exception::Class -HTML::Mason 1.16 -MLDBM -Errno -FreezeThaw -Digest::MD5 2.27 -CGI::Cookie 1.20 -Storable 2.08 -Apache::Session 1.53 -. - -$deps{'MAILGATE'} = [ _( << '.') ]; -HTML::TreeBuilder -HTML::FormatText -Getopt::Long -LWP::UserAgent -. - -$deps{'CLI'} = [ _( << '.') ]; -Getopt::Long 2.24 -. - -$deps{'DEV'} = [ _( << '.') ]; -Regexp::Common -Time::HiRes -Test::Inline -WWW::Mechanize -. - -$deps{'FASTCGI'} = [ _( << '.') ]; -CGI 2.92 -FCGI -CGI::Fast -. - -$deps{'SPEEDYCGI'} = [ _( << '.') ]; -CGI 2.92 -CGI::SpeedyCGI -. - - -$deps{'MODPERL1'} = [ _( << '.') ]; -CGI 2.92 -Apache::Request -Apache::DBI 0.92 -. - -$deps{'MODPERL2'} = [ _( << '.') ]; -CGI 2.92 -Apache::DBI -. - -$deps{'I18N-COMPAT'} = [ _( << '.') ]; -Text::Iconv -Encode::compat 0.04 -. - -$deps{'MYSQL'} = [ _( << '.') ]; -DBD::mysql 2.1018 -. -$deps{'ORACLE'} = [ _( << '.') ]; -DBD::Oracle -. -$deps{'POSTGRESQL'} = [ _( << '.') ]; -DBD::Pg -. - -print "perl:\n"; -print "\t5.8.0"; -eval {require 5.008}; -if ($@) { -print "...missing.\n"; - eval {require 5.006001}; - if ($@) { - print " RT is known to be non-functional on versions of perl older than 5.6.1. Please upgrade to 5.8.0 or newer"; - die; - } else { - print " RT is not supported on perl 5.6.1\n"; - } -} else { - print "...found\n"; - -} - - -foreach my $type (keys %args) { -next unless ($type =~ /^with-(.*?)$/); -my $type = $1; -print "$type dependencies:\n"; - my @deps = (@{$deps{$type}}); - while (@deps) { - my $module = shift @deps; - my $version = shift @deps; -my $ret; - $ret =test_dep($module, $version); - -if ($args{'install'} && !$ret) { - resolve_dep($module); -} -} -} -sub test_dep { - my $module = shift; - my $version = shift; - - print "\t$module $version"; - eval "use $module $version" ; - if ($@) { - my $error = $@; - $error =~ s/\n(.*)$//s; - print "...MISSING\n"; - print "\t\t$error\n" if $error =~ /this is only/; - - return undef; - } else { - print "...found\n"; -return 1; - } -} - -sub resolve_dep { - my $module = shift; - use CPAN; - CPAN::Shell->install($module); - -} - - -sub print_help { - print << "EOF"; - -$0 FLAG DBTYPE - - -$0 is a tool for RT that will tell you if you've got all -the modules RT depends on properly installed. - -Flags: (only one flag is valid for a given run) - --quiet will check to see if we've got everything we need - and will exit with a return code of (1) if we don't. - --warn will tell you what isn't properly installed - --fix will use CPANPLUS.pm or CPAN.pm to magically make everything better - -DBTYPE is one of: - oracle, pg, mysql - -EOF - - exit(0); -} |