[Rt-commit] rt branch, 3.9-trunk, updated. rt-3.8.8-696-ga7f761c

Jesse Vincent jesse at bestpractical.com
Mon Sep 6 19:28:48 EDT 2010


The branch, 3.9-trunk has been updated
       via  a7f761ca40657ecec634ca32c9766be8fab3252e (commit)
       via  8747ab26c6eb349bd210ba7ac68e7d653bb864a9 (commit)
       via  df8d7d0de45497c74c1a7eb269c2cb9e7f6c3f55 (commit)
      from  d885f6b4bf1565f78c519b144ec368032430b9c2 (commit)

Summary of changes:
 lib/RT/Interface/Web.pm                            |   15 +
 share/html/Elements/Login                          |    6 +-
 share/html/Ticket/Display.html                     |    6 +
 share/html/index.html                              |    5 +
 share/html/m/_elements/footer                      |    8 +
 share/html/m/_elements/full_site_link              |    1 +
 share/html/m/_elements/header                      |   24 ++
 share/html/m/_elements/menu                        |   62 +++
 share/html/m/_elements/raw_style                   |  415 +++++++++++++++++++
 share/html/m/_elements/ticket_list                 |   64 +++
 share/html/m/_elements/ticket_menu                 |   31 ++
 share/html/m/_elements/wrapper                     |   15 +
 share/html/m/dhandler                              |    5 +
 share/html/m/index.html                            |    4 +
 share/html/m/login                                 |   84 ++++
 share/html/m/logout                                |    7 +
 share/html/m/style.css                             |    5 +
 share/html/m/ticket/create                         |  400 ++++++++++++++++++
 share/html/m/ticket/history                        |   31 ++
 .../schema.Oracle => share/html/m/ticket/modify    |    0
 share/html/m/ticket/reply                          |  171 ++++++++
 share/html/m/ticket/select_create_queue            |   18 +
 share/html/m/ticket/show                           |  431 ++++++++++++++++++++
 share/html/m/tickets/requested                     |    4 +
 share/html/m/tickets/search                        |   64 +++
 25 files changed, 1875 insertions(+), 1 deletions(-)
 create mode 100644 share/html/m/_elements/footer
 create mode 100644 share/html/m/_elements/full_site_link
 create mode 100644 share/html/m/_elements/header
 create mode 100644 share/html/m/_elements/menu
 create mode 100644 share/html/m/_elements/raw_style
 create mode 100644 share/html/m/_elements/ticket_list
 create mode 100644 share/html/m/_elements/ticket_menu
 create mode 100644 share/html/m/_elements/wrapper
 create mode 100644 share/html/m/dhandler
 create mode 100644 share/html/m/index.html
 create mode 100644 share/html/m/login
 create mode 100644 share/html/m/logout
 create mode 100644 share/html/m/style.css
 create mode 100644 share/html/m/ticket/create
 create mode 100644 share/html/m/ticket/history
 copy etc/upgrade/3.3.11/schema.Oracle => share/html/m/ticket/modify (100%)
 create mode 100644 share/html/m/ticket/reply
 create mode 100644 share/html/m/ticket/select_create_queue
 create mode 100644 share/html/m/ticket/show
 create mode 100644 share/html/m/tickets/requested
 create mode 100644 share/html/m/tickets/search

- Log -----------------------------------------------------------------
commit df8d7d0de45497c74c1a7eb269c2cb9e7f6c3f55
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Wed Sep 1 14:40:20 2010 -0400

    Initial import of the mobile UI extension as a core feature

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 90e3f3f..b5e60a8 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -656,6 +656,21 @@ sub SendStaticFile {
     close $fh;
 }
 
+
+
+sub MobileClient {
+    my $self = shift;
+
+
+if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS)/io && !$HTML::Mason::Commands::session{'NotMobile'})  {
+    return 1;
+} else {
+    return undef;
+}
+
+}
+
+
 sub StripContent {
     my %args    = @_;
     my $content = $args{Content};
diff --git a/share/html/Elements/Login b/share/html/Elements/Login
index 00a8ab8..94d658e 100755
--- a/share/html/Elements/Login
+++ b/share/html/Elements/Login
@@ -46,12 +46,16 @@
 %# 
 %# END BPS TAGGED BLOCK }}}
 <%INIT>
-if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
+if ( $m->request_comp->path =~ '^/REST/\d+\.\d+/' ) {
     $r->content_type("text/plain");
     $m->error_format("text");
     $m->out("RT/$RT::VERSION 401 Credentials required\n");
     $m->out("\n$Error\n") if $Error;
     $m->abort;
+} elsif (    RT::Interface::Web->MobileClient()
+          || ( $m->request_comp->path() =~ m{^/m(?:\/|$)} ) ) {
+    $m->comp( '/m / login ', %ARGS );
+    $m->abort;
 }
 
 my $req_uri;
diff --git a/share/html/Ticket/Display.html b/share/html/Ticket/Display.html
index 0e6d761..a273df5 100755
--- a/share/html/Ticket/Display.html
+++ b/share/html/Ticket/Display.html
@@ -100,6 +100,12 @@ $Collapsed => undef
 
 $m->callback( TicketObj => $TicketObj, ARGSRef => \%ARGS, CallbackName => 'Initial' );
 
+if ( ! $ARGS{'NoRedirect'} && RT::Interface::Web->MobileClient()) {
+    RT::Interface::Web::Redirect(RT->Config->Get('WebURL').'/m/ticket/show.html?id='.$TicketObj->id);
+    $m->abort;
+}
+
+
 my (@Actions, $Tickets, $title);
 
 
diff --git a/share/html/index.html b/share/html/index.html
index 60eb0a5..2c3c10b 100755
--- a/share/html/index.html
+++ b/share/html/index.html
@@ -86,6 +86,11 @@ If you need commercial support, please contact us at sales at bestpractical.com.
 <& /Elements/MyRT &>
 <%init>
 
+if ( RT::Interface::Web->MobileClient()) {
+    RT::Interface::Web::Redirect(RT->Config->Get('WebURL') ."m/");
+    $m->abort();
+}
+
 my @results;
 my $skip_create = 0;
 
diff --git a/share/html/m/_elements/footer b/share/html/m/_elements/footer
new file mode 100644
index 0000000..2c7602d
--- /dev/null
+++ b/share/html/m/_elements/footer
@@ -0,0 +1,8 @@
+  <div id="bpscredits">
+    <& /Elements/Logo, ShowName => 0 &>
+    <div id="copyright">
+<&|/l,     '', '', '2010', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
+</div>
+</div>
+</body>
+</html>
diff --git a/share/html/m/_elements/full_site_link b/share/html/m/_elements/full_site_link
new file mode 100644
index 0000000..c17dfdb
--- /dev/null
+++ b/share/html/m/_elements/full_site_link
@@ -0,0 +1 @@
+<a id="fullsite" href="<%RT->Config->Get('WebPath')%>/m?NotMobile=1"><&|/l&>Not using a mobile browser?</&></a>
diff --git a/share/html/m/_elements/header b/share/html/m/_elements/header
new file mode 100644
index 0000000..2c20383
--- /dev/null
+++ b/share/html/m/_elements/header
@@ -0,0 +1,24 @@
+<%args>
+$title => undef
+$show_home_button => 1
+</%args>
+<%init>
+$r->headers_out->{'Pragma'} = 'no-cache';
+$r->headers_out->{'Cache-control'} = 'no-cache';
+</%init>
+<html>
+<head>
+<link rel="stylesheet" type="text/css" href="<%RT->Config->Get('WebPath')|n%>/m/style.css"/>
+<title><%$title%></title>
+<meta name="viewport" content="width=device-width height=device-height user-scalable=yes"/>
+</head>
+<body>
+% if ($show_home_button) {
+% # The align is for older browsers, like the blackberry
+<div id="gohome" align="right">
+<a href="<%RT->Config->Get('WebPath')%>/m/"><&|/l&>Homepage</&></a>
+</div>
+% }
+% if ($title) {
+<h1><%$title%></h1>
+% }
diff --git a/share/html/m/_elements/menu b/share/html/m/_elements/menu
new file mode 100644
index 0000000..e13c1c7
--- /dev/null
+++ b/share/html/m/_elements/menu
@@ -0,0 +1,62 @@
+<&| /Widgets/TitleBox, class => 'menu'&>
+<ul class="menu">
+% for my $item (@menu) {
+% if (exists $item->{html}) {
+<li><%$item->{html} |n%></li>
+% } else {
+<li><a href="<%RT->Config->Get('WebPath')%><%$item->{url}%>"><%$item->{label}%></a></li>
+% }
+% }
+</ul>
+</&>
+<%init>
+use RT::SavedSearches;
+my @menu = (
+    {         html => '<form method="GET" id="search" action="'
+            . RT->Config->Get('WebPath')
+            . '/m/tickets/search">'
+            . loc("Search")
+            . ': <input type="text" name="q" id="q" value=""/>'
+            . '<input type="submit" value="'
+            . loc("Go")
+            . '"/></form>'
+    },
+    {   label => loc("New ticket"),
+        url   => '/m/ticket/select_create_queue',
+    },
+    {   label => loc("Bookmarked tickets"),
+        url   => '/m/tickets/search?name=Bookmarked%20Tickets',
+    },
+    {   label => loc("Tickets I own"),
+        url   => '/m/tickets/search?name=My%20Tickets',
+    },
+    {   label => loc("Unowned tickets"),
+        url   => '/m/tickets/search?name=Unowned%20Tickets',
+    },
+    {   label => loc("All tickets"),
+        url   => '/m/tickets/search?query=id!%3d0&order_by=id&order=DESC'
+    },
+);
+
+
+if ( $session{'CurrentUser'}->HasRight( Right  => 'LoadSavedSearch', Object => $RT::System))
+    {
+
+        my @Objects = RT::SavedSearches->new( $session{CurrentUser} )->_PrivacyObjects;
+        push @Objects, RT::System->new( $session{'CurrentUser'} )
+            if $session{'CurrentUser'}->HasRight(
+            Object => $RT::System,
+            Right  => 'SuperUser'
+            );
+
+        foreach my $object (@Objects) {
+            my @searches = $object->Attributes->Named('SavedSearch');
+            foreach my $search (@searches) {
+                next unless $search->SubValue("SearchType") eq 'Ticket';
+                push @menu, { label => $search->Description, url => '/m/tickets/search?query=' . $search->SubValue("Query").'&order='.$search->SubValue("Order").'&order_by='.$search->SubValue("OrderBy") };
+
+            }
+        }
+}
+push @menu,  {   label => loc("Logout"), url   => '/m/logout', } ;
+</%init>
diff --git a/share/html/m/_elements/raw_style b/share/html/m/_elements/raw_style
new file mode 100644
index 0000000..c97a900
--- /dev/null
+++ b/share/html/m/_elements/raw_style
@@ -0,0 +1,415 @@
+body {
+    font-family: helvetica, arial, sans-serif;
+    background-color: #ccf;
+    margin: 0;
+}
+
+h1 { 
+    font-size: 1.2em;
+    padding-top: 0.5em;
+    padding-left: 0.2em;
+    display: block; 
+    background-color: #fff;
+
+}
+
+div.buttons {
+    text-align: right;
+    padding-right: 0.5em;
+    padding-bottom: 0.5em;
+}
+
+.titlebox-title {
+    font-size: 1.1em;
+    margin-left: 0.5em;
+    margin-top: -1.2em;
+    top: -0.5em;
+    padding: 0.5em;
+    position: relative;
+    display: inline-block;
+    text-decoration: none;
+    background-color: #fff;
+    -moz-border-radius: 0.25em;
+    -webkit-border-radius: 0.25em;
+    -webkit-box-shadow: #333 0px 0px 5px;
+    -moz-box-shadow: #333 0px 0px 5px;
+    box-shadow: #333 0px 0px 5px;
+}
+
+ul.menu
+{
+    text-align: left;
+    list-style: none;
+    padding: 0;
+    margin: -0.6em;
+    left: 0;
+}
+
+ul.menu li
+{
+    display: block;
+    margin: 0;
+    padding: 0;
+    font-weight: bold;
+}
+
+ul.ticketlist li:active, ul.ticketlist li:hover,
+ul.menu li:active, ul.menu li:hover {
+    background-color: #eee;
+}
+
+
+ul.menu li
+{
+    display: block;
+    padding: 1em;
+    margin: 0;
+    border:0;
+    border-top-width: 1px;
+    border-top-color: #666;
+    border-style: solid;
+    text-decoration: none;
+}
+
+ul.menu li:first-child{
+    border: none;
+}
+
+ul.menu li#active a
+{
+    color: #800000;
+}
+
+div.titlebox, #bpscredits, .ticket_menu{
+    -moz-border-radius: 1em;
+    -webkit-border-radius: 1em;
+    margin: 0.5em;
+    background-color: #fff;
+    padding-top: 1em;
+    padding-bottom: 0.8em;
+    margin-top: 1.25em;
+    -webkit-box-shadow: #333 0px 0px 5px;
+    -moz-box-shadow: #333 0px 0px 5px;
+    box-shadow: #333 0px 0px 5px;
+    margin-bottom: 1em;
+}
+
+div .titlebox-content {
+    padding-left: 0.5em;
+    padding-right: 0.5em;
+}
+
+hr.clear {
+    display: none;
+}
+
+
+.label, .labeltop {
+    font-weight: normal;
+}
+.value { 
+    font-weight: bold;
+    display:inline-block;
+}
+
+ul.ticketlist {
+    list-style: none;
+    padding-left: -0.5em;
+    padding-right: -0.5em; /* to counteract the titlebox and get shading to the end*/
+    margin-left: -0.5em;
+    margin-right: -0.5em;
+    padding: 0em;
+    padding-bottom: 1em;
+}
+
+ul.ticketlist li.ticket {
+    padding: 0.5em;
+    font-weight: bold;
+    border-bottom: 1px solid #999;
+    
+}
+ul.ticketlist li.ticket:first-child {
+    border-top: 1px solid #999;
+}
+
+ul.ticketlist li.ticket a.ticket{
+    display: inline-block;
+    font-size: 1em;
+    width: 100%;
+    padding: 0.5em;
+    padding-bottom: 5em;
+    margin-bottom: -5em;
+}
+ul.ticketlist li.ticket div.metadata {
+}
+
+
+ul.ticketlist li.ticket div.metadata div {
+    padding: 0.2em;
+    font-size:0.8em;
+    display: block;
+}
+
+ul.ticketlist li.ticket div.metadata .label {
+    display: inline-block;
+    width: 6em;
+    font-size: 0.8em;
+    text-align: right;
+    color: #666;
+}
+
+div#paging {
+    text-align: center;
+}
+
+.ticket-reply .titlebox-title, .titlebox.search .titlebox-title, .titlebox.menu .titlebox-title, .ticket_menu .titlebox-title, .history .titlebox-title, #ticket-create-basics .titlebox-title{
+    display: none;
+}
+
+a {
+    color: #000;
+}
+
+.ticket_menu a, .menu a {
+    text-decoration: none;
+}
+
+ul.menu a {
+    padding: 0.5em;
+    margin-top: -0.5em;
+    margin-bottom: -0.5em;
+    display: inline-block;
+    width: 100%;
+}
+
+ul.menu a:after {
+    color: #666;
+    float: right;
+    content: ">";
+    font-size: 1.5em;
+    padding: 0;
+    margin: 0;
+    padding-right: 1em;
+
+}
+
+ul.menu form {
+    display: inline;
+}
+
+ul.menu form * {
+    display: inline;
+}
+
+
+ul.menu form input[type=text] { 
+    width: 7em;
+}
+
+ul.menu form input{ 
+
+    width: auto;
+    padding: 0.5em;
+    margin: -0.5em;
+    margin-left: 1em;
+}
+
+.ticket_menu {
+    text-align: center;
+}
+
+.ticket_menu ul {
+    display: block;
+    margin: 0;
+    padding: 0;
+}
+
+.ticket_menu ul li {
+
+    display: inline-block;
+    text-align: center;
+    padding-bottom: 0.25em;
+    padding-top: 0.25em;
+    font-size: 1em;
+    width: 28%;
+    padding-right: 0.3em;
+    padding-left: 0.2em;
+    border-right: 1px solid #000;
+}
+.ticket_menu ul li:last-child {
+    padding-right: 0;
+    border-right: 0; 
+}
+
+.ticket-info-reminders table {
+
+    width: 100%;
+}
+
+#ticket-create .label:after {
+   content: ": "; 
+    padding-right: 0.25em;
+
+}
+
+#ticket-create .content-label {
+    width: auto;
+    display: block;
+    text-align: left;
+    
+}
+
+#ticket-show .label, .login-body .label {
+    display: inline-block;
+    text-align: right;
+    width: 6em;
+    padding-right: 0.25em;
+    font-size: 0.8em;
+}
+
+.login-body .value {
+    width: auto;
+}
+
+.history ul.history-list {
+    padding: 0;
+    margin: 0;
+    padding-bottom: 2em;
+}
+
+
+.history ul.history-list li:first-child {
+    border-top: 1px solid #ccc;
+}
+
+.history ul.history-list li {
+    list-style: none;
+    border-bottom: 1px solid #ccc;
+    padding: 0.5em;
+}
+
+.history .age {
+    display: inline-block;
+    min-width: 8em;
+    text-align: right;
+
+}
+
+div#login-box div.titlebox {
+    width: 100%;
+    margin-left:auto;
+    margin-right: auto;
+}
+
+div#login-box input[type=text], div#login-box input[type=password] {
+    width: 100%;
+}
+
+#bpscredits img {
+    padding-bottom: 1em;
+}
+
+
+
+#bpscredits {
+    float: right;
+    text-align: right;
+    width: auto;
+    font-size: 0.8em;
+    padding: 1em;
+}
+
+
+:focus {
+    background-color: #ffc;
+    border-color: #000;
+    border-weight: 3px;
+}
+
+input[type=submit], input[type=button], button, #paging a {
+    border: 2px outset;
+    margin: 0.3em;
+    padding: 0.3em;
+    padding-left: 0.6em;
+    padding-right: 0.6em;
+    -moz-border-radius: 0.5em;
+    -webkit-border-radius: 0.5em;
+    background-color: #006699;
+    color: #fff;
+}
+
+form { 
+
+    margin:0;
+}
+
+#gohome {
+    position: absolute;
+    top: 0;
+    right: 0;
+    border-left: 1px solid black;
+    border-bottom: 1px solid black;
+    -moz-border-radius-bottomleft: 1em;
+    -webkit-border-bottom-left-radius: 1em;
+    padding: 0.5em;
+    background-color: #fff;
+}
+
+#gohome a {
+    font-size: 1em;
+    padding: 0.25em;
+    color: #000;
+}
+
+div.txn-content {
+    
+    font-size:0.8em;
+    padding-left:1em;
+    padding-top:0.5em;
+    margin-top: 0.5em;
+    margin-left: 2em;
+    padding-bottom: 0.5em;
+    border-left: 5px solid #00c;
+
+}
+
+.label {
+    text-align: left;
+    width: 10em;
+    color: #666;
+    display: block;
+    padding-bottom: 0.2em;
+    padding-right: 0.2em;
+    
+}
+
+div.entry, tr.input-row {
+    margin-bottom: 0.25em;
+    padding-bottom: 0.25em;
+    border-bottom: 1px solid #ccc;
+    display: block;
+    width: 100%;
+    min-height: 1em;
+}
+
+
+input, input[type=text], input[type=password], select {
+    width: 100%;
+}
+
+.timefield input {
+    width: 5em;
+}
+
+.timefield select {
+    width: auto;
+}
+
+
+textarea {
+    width: 100%;
+}
+
+a#fullsite {
+    padding-left: 1em;
+}
diff --git a/share/html/m/_elements/ticket_list b/share/html/m/_elements/ticket_list
new file mode 100644
index 0000000..43a54c6
--- /dev/null
+++ b/share/html/m/_elements/ticket_list
@@ -0,0 +1,64 @@
+<%args>
+$order => undef
+$order_by => undef
+$query => ''
+$page => 1
+</%args>
+<%init>
+my $collection = RT::Tickets->new($session{'CurrentUser'});
+$collection->FromSQL($query);
+$collection->RowsPerPage(10);
+$collection->GotoPage($page-1);
+# XXX: ->{'order_by'} is hacky, but there is no way to check if
+# collection is ordered or not
+if ( $order_by) {
+        my @order_by = split /\|/, $order_by;
+        my @order = split /\|/,$order;
+    $collection->OrderByCols(
+        map { { FIELD => $order_by[$_], ORDER => $order[$_] } }
+        ( 0 .. $#order_by )
+    );
+}
+
+
+
+$collection->RedoSearch();
+ 
+if ($page > 1 && ! @{$collection->ItemsArrayRef||[]}) {
+    RT::Interface::Web::Redirect( RT->Config->Get('WebURL')."/m/tickets/search?page=".($page-1)."&query=".$query."&order=$order&order_by=$order_by");
+}
+
+</%init>
+<&| /m/_elements/wrapper, title => 
+loc("Found [quant,_1,ticket]",$collection->Count) &>
+<&|/Widgets/TitleBox, class => 'search'
+&>
+<ul class="ticketlist">
+% while (my $ticket = $collection->Next()) {
+<li class="ticket">
+<a class="ticket" href="<%RT->Config->Get('WebPath')%>/m/ticket/show?id=<%$ticket->id%>"><%$ticket->id%>: <%$ticket->Subject%></a>
+<div class="metadata">
+<%perl>
+
+</%perl>
+<div class="requestors"><span class="label"><&|/l&>Requestors</&>:</span> <& /Ticket/Elements/ShowGroupMembers, Group => $ticket->Requestors, Ticket => $ticket &></div>
+<div class="status"><span class="label"><&|/l&>Status</&>:</span> <%$ticket->Status%></div>
+<div class="owner"><span class="label"><&|/l&>Owner</&>:</span> <& /Elements/ShowUser, User => $ticket->OwnerObj, Ticket => $ticket &></div>
+<div class="created"><span class="label"><&|/l&>Created</&>:</span> <%$ticket->CreatedObj->AgeAsString()%></div>
+% if ($ticket->Priority) {
+<div class="priority"><span class="label"><&|/l&>Priority</&>:</span> <%$ticket->Priority%></div>
+% }
+</div>
+</li>
+% }
+</ul>
+<div id="paging">
+% if ($page > 1) { 
+<a href="<%RT->Config->Get('WebPath')%>/m/tickets/search?page=<%$page-1%>&query=<%$query%>&order=<%$order%>&order_by=<%$order_by%>">Back</a>
+% }
+Page <%$page%>
+
+<a href="<%RT->Config->Get('WebPath')%>/m/tickets/search?page=<%$page+1%>&query=<%$query%>&order=<%$order%>&order_by=<%$order_by%>">Next</a>
+</div>
+</&>
+</&>
diff --git a/share/html/m/_elements/ticket_menu b/share/html/m/_elements/ticket_menu
new file mode 100644
index 0000000..257b066
--- /dev/null
+++ b/share/html/m/_elements/ticket_menu
@@ -0,0 +1,31 @@
+<%args>
+$ticket
+</%args>
+<div class="ticket_menu">
+<ul>
+% for my $item (@menu) {
+<li><a href="<%RT->Config->Get('WebPath')%><%$item->{url}%>"><%$item->{label}%></a></li>
+% }
+</ul>
+</div>
+<%init>
+my @menu = ( 
+{ label =>  loc("Basics"),
+  url => '/m/ticket/show?id='.$ticket->id
+},
+ {
+    label => loc("History"),
+  url => '/m/ticket/history?id='.$ticket->id
+    },
+ #{ label => loc("Modify"), url => '/m/ticket/modify?id='.$ticket->id },
+{
+    label => loc("Reply"),
+    url => '/m/ticket/reply?id='.$ticket->id
+}
+
+
+); 
+
+my $width = int(100/ ($#menu +1))-5;
+
+</%init>
diff --git a/share/html/m/_elements/wrapper b/share/html/m/_elements/wrapper
new file mode 100644
index 0000000..794385d
--- /dev/null
+++ b/share/html/m/_elements/wrapper
@@ -0,0 +1,15 @@
+<%args>
+$title => ''
+$show_home_button => 1
+</%args>
+<%init>
+if ($m->request_args->{'NotMobile'}) {
+    $session{'NotMobile'} = 1;
+    RT::Interface::Web::Redirect(RT->Config->Get('WebURL'));
+    $m->abort();
+}
+$m->comp('header', title => $title, show_home_button => $show_home_button);
+$m->out($m->content);
+$m->comp('footer');
+$m->abort();
+</%init>
diff --git a/share/html/m/dhandler b/share/html/m/dhandler
new file mode 100644
index 0000000..627ec22
--- /dev/null
+++ b/share/html/m/dhandler
@@ -0,0 +1,5 @@
+<%init>
+# deal with users who don't have options indexes set right
+RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."m/index.html");
+$m->abort();
+</%init>
diff --git a/share/html/m/index.html b/share/html/m/index.html
new file mode 100644
index 0000000..5b38125
--- /dev/null
+++ b/share/html/m/index.html
@@ -0,0 +1,4 @@
+<&| _elements/wrapper, title => loc("RT for [_1]",RT->Config->Get('rtname'))&>
+<& _elements/menu &>
+<& _elements/full_site_link &>
+</&>
diff --git a/share/html/m/login b/share/html/m/login
new file mode 100644
index 0000000..3688749
--- /dev/null
+++ b/share/html/m/login
@@ -0,0 +1,84 @@
+<%INIT>
+
+my $req_uri;
+
+if (UNIVERSAL::can($r, 'uri') and $r->uri =~ m{.*/m/(.*)}) {
+    $req_uri = '/m/'.$1;
+}
+
+my $default_path = RT->Config->Get('WebPath') ."/m/";
+
+my $form_action = defined $goto             ? $goto
+                : defined $req_uri          ? $req_uri
+                :                             $default_path
+                ;
+
+# sanitize $form_action
+my $uri = URI->new($form_action);
+
+# You get undef scheme with a relative uri like "/Search/Build.html"
+unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
+    $form_action = $default_path;
+}
+
+# Make sure we're logging in to the same domain
+# You can get an undef authority with a relative uri like "index.html"
+my $uri_base_url = URI->new(RT->Config->Get('WebURL')."/m/");
+unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
+    $form_action = $default_path;
+}
+</%INIT>
+<&| /m/_elements/wrapper, show_home_button => 0 &>
+<style>
+<& /m/_elements/raw_style &>
+</style>
+<h1><&|/l, RT->Config->Get('rtname') &>RT for [_1]</&></h1>
+<div id="body" class="login-body">
+% if ($Error) {
+<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0, class => 'error'  &>
+<% $Error %>
+</&>
+% }
+
+
+<div id="login-box">
+<&| /Widgets/TitleBox, title => loc('Login'), hideable => 0 &>
+
+% unless (RT->Config->Get('WebExternalAuth') and !RT->Config->Get('WebFallbackToInternalAuth')) {
+<form id="login" name="login" method="post" action="<% $form_action %>">
+
+<div class="entry">
+    <span class="label"><&|/l&>Username</&>:</span><span class="value"><input name="user" value="<%$user%>" id="user" /></span>
+</div>
+
+<div class="entry">
+    <span class="label"><&|/l&>Password</&>:</span><span class="value"><input type="password" name="pass"/></span>
+</div>
+
+<& /Elements/Submit, Label => loc('Login')&>
+
+% foreach my $key (keys %ARGS) {
+%  if (($key ne 'user') and ($key ne 'pass')) {
+% 	if (ref($ARGS{$key}) =~ /ARRAY/) {
+% 		foreach my $val (@{$ARGS{$key}}) {
+<input type="hidden" class="hidden" name="<%$key %>" value="<% $val %>" />
+% 		}
+% 	}
+%	else {
+<input type="hidden" class="hidden" name="<% $key %>" value="<% $ARGS{$key} %>" />
+% 	}
+%  }
+% }
+</form>
+% }
+</&>
+</div><!-- #login-box -->
+</div><!-- #login-body -->
+<& _elements/full_site_link &>
+</&>
+<%ARGS>
+$user => ""
+$pass => undef
+$goto => undef
+$Error => undef
+</%ARGS>
diff --git a/share/html/m/logout b/share/html/m/logout
new file mode 100644
index 0000000..78878a3
--- /dev/null
+++ b/share/html/m/logout
@@ -0,0 +1,7 @@
+<%init>
+if (keys %session) {
+    tied(%session)->delete;
+    $session{'CurrentUser'} = RT::CurrentUser->new;
+}
+RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."/m");
+</%init>
diff --git a/share/html/m/style.css b/share/html/m/style.css
new file mode 100644
index 0000000..22be0a9
--- /dev/null
+++ b/share/html/m/style.css
@@ -0,0 +1,5 @@
+<%init>
+    $HTML::Mason::Commands::r->content_type('text/css');
+    $m->comp('/m/_elements/raw_style');
+    $m->abort();
+</%init>
diff --git a/share/html/m/ticket/create b/share/html/m/ticket/create
new file mode 100644
index 0000000..7c23194
--- /dev/null
+++ b/share/html/m/ticket/create
@@ -0,0 +1,400 @@
+<%ARGS>
+$QuoteTransaction => undef
+$CloneTicket => undef
+</%ARGS>
+<%init>
+$m->callback( CallbackName => "Init", ARGSRef => \%ARGS );
+my $Queue = $ARGS{Queue};
+
+
+my $showrows = sub {
+    my @pairs = @_;
+
+    while (@pairs) {
+        my $key = shift @pairs;
+        my $val = shift @pairs;
+
+        $m->out("<div class=\"entry\"><span class=\"label\">$key</span><div class=\"value\">$val</div></div>");
+
+    }
+
+};
+
+
+my $CloneTicketObj;
+if ($CloneTicket) {
+    $CloneTicketObj = RT::Ticket->new( $session{CurrentUser} );
+    $CloneTicketObj->Load($CloneTicket)
+        or Abort( loc("Ticket could not be loaded") );
+
+    my $clone = {
+        Requestors => join( ',', $CloneTicketObj->RequestorAddresses ),
+        Cc         => join( ',', $CloneTicketObj->CcAddresses ),
+        AdminCc    => join( ',', $CloneTicketObj->AdminCcAddresses ),
+        InitialPriority => $CloneTicketObj->Priority,
+    };
+
+    $clone->{$_} = $CloneTicketObj->$_()
+        for qw/Owner Subject FinalPriority TimeEstimated TimeWorked
+        Status TimeLeft/;
+
+    $clone->{$_} = $CloneTicketObj->$_->AsString
+        for grep { $CloneTicketObj->$_->Unix }
+        map      { $_ . "Obj" } qw/Starts Started Due Resolved/;
+
+    my $members = $CloneTicketObj->Members;
+    my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by );
+    my $refers = $CloneTicketObj->RefersTo;
+    while ( my $refer = $refers->Next ) {
+        push @refers, $refer->LocalTarget;
+    }
+    $clone->{'new-RefersTo'} = join ' ', @refers;
+
+    my $refers_by = $CloneTicketObj->ReferredToBy;
+    while ( my $refer_by = $refers_by->Next ) {
+        push @refers_by, $refer_by->LocalBase;
+    }
+    $clone->{'RefersTo-new'} = join ' ', @refers_by;
+    if (0) {    # Temporarily disabled
+        my $depends = $CloneTicketObj->DependsOn;
+        while ( my $depend = $depends->Next ) {
+            push @depends, $depend->LocalTarget;
+        }
+        $clone->{'new-DependsOn'} = join ' ', @depends;
+
+        my $depends_by = $CloneTicketObj->DependedOnBy;
+        while ( my $depend_by = $depends_by->Next ) {
+            push @depends_by, $depend_by->LocalBase;
+        }
+        $clone->{'DependsOn-new'} = join ' ', @depends_by;
+
+        while ( my $member = $members->Next ) {
+            push @members, $member->LocalBase;
+        }
+        $clone->{'MemberOf-new'} = join ' ', @members;
+
+        my $members_of = $CloneTicketObj->MemberOf;
+        while ( my $member_of = $members_of->Next ) {
+            push @members_of, $member_of->LocalTarget;
+        }
+        $clone->{'new-MemberOf'} = join ' ', @members_of;
+
+    }
+
+    my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields();
+    while ( my $cf = $cfs->Next ) {
+        my $cf_id     = $cf->id;
+        my $cf_values = $CloneTicketObj->CustomFieldValues( $cf->id );
+        my @cf_values;
+        while ( my $cf_value = $cf_values->Next ) {
+            push @cf_values, $cf_value->Content;
+        }
+        $clone->{"Object-RT::Ticket--CustomField-$cf_id-Value"} = join "\n",
+            @cf_values;
+    }
+
+    for ( keys %$clone ) {
+        $ARGS{$_} = $clone->{$_} if not defined $ARGS{$_};
+    }
+
+}
+
+my @results;
+
+my $title = loc("Create a ticket");
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue) || Abort(loc("Queue could not be loaded."));
+
+$m->callback( QueueObj => $QueueObj, title => \$title, results => \@results, ARGSRef => \%ARGS );
+
+$QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."));
+
+my $CFs = $QueueObj->TicketCustomFields();
+
+my $ValidCFs = $m->comp(
+    '/Elements/ValidateCustomFields',
+    CustomFields => $CFs,
+    ARGSRef => \%ARGS
+);
+
+# {{{ deal with deleting uploaded attachments
+foreach my $key (keys %ARGS) {
+    if ($key =~ m/^DeleteAttach-(.+)$/) {
+	delete $session{'Attachments'}{$1};
+    }
+    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
+}
+# }}}
+
+# {{{ store the uploaded attachment in session
+if ($ARGS{'Attach'}) {			# attachment?
+    my $attachment = MakeMIMEEntity(
+        AttachmentFieldName => 'Attach'
+    );
+
+    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
+    $session{'Attachments'} = {
+        %{$session{'Attachments'} || {}},
+	$file_path => $attachment,
+    };
+}
+# }}}
+
+# delete temporary storage entry to make WebUI clean
+unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
+    delete $session{'Attachments'};
+}
+
+my $checks_failure = 0;
+
+my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
+$m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
+    self      => $gnupg_widget,
+    QueueObj  => $QueueObj,
+);
+
+
+if ( !exists $ARGS{'AddMoreAttach'} && ($ARGS{'id'}||'') eq 'new' ) {
+    my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
+        self      => $gnupg_widget,
+        Operation => 'Create',
+        QueueObj  => $QueueObj,
+    );
+    $checks_failure = 1 unless $status;
+}
+
+# check email addresses for RT's
+{
+    foreach my $field ( qw(Requestors Cc AdminCc) ) {
+        my $value = $ARGS{ $field };
+        next unless defined $value && length $value;
+
+        my @emails = Email::Address->parse( $value );
+        foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) {
+            push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc($field =~ /^(.*?)s?$/) );
+            $checks_failure = 1;
+            $email = undef;
+        }
+        $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails;
+    }
+}
+
+my $skip_create = 0;
+$m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create => \$skip_create, 
+              checks_failure => $checks_failure, results => \@results );
+
+if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} eq 'new')) { # new ticket?
+    if ( $ValidCFs && !$checks_failure && !$skip_create ) {
+        $m->comp('show', %ARGS);
+        $RT::Logger->crit("After display call; error is $@");
+        $m->abort();
+    }
+    elsif ( !$ValidCFs ) {
+        # Invalid CFs
+        while (my $CF = $CFs->Next) {
+            my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
+            push @results, $CF->Name . ': ' . $msg;
+        }
+    }
+}
+
+
+
+
+</%init>
+<&| /m/_elements/wrapper, title => $title &>
+<& /Elements/ListActions, actions => \@results  &>
+<form action="<% RT->Config->Get('WebPath') %>/m/ticket/create" method="post" enctype="multipart/form-data" name="TicketCreate" id="ticket-create">
+<input type="hidden" class="hidden" name="id" value="new" />
+% $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
+% if ($gnupg_widget) {
+<& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+% }
+
+
+<div id="ticket-create-simple">
+<&| /Widgets/TitleBox, title => $QueueObj->Name &>
+
+<%perl>
+$showrows->(
+    loc("Subject") => '<input name="Subject" size="30" maxsize="200" value="'.($ARGS{Subject} || '').'" />');
+</%perl>
+    <span class="content-label label"><%loc("Describe the issue below")%></span>
+        <& /Elements/MessageBox, exists $ARGS{Content}  ? (Default => $ARGS{Content}, IncludeSignature => 0 ) : ( QuoteTransaction => $QuoteTransaction ), Height => 5  &>
+
+
+<&/Elements/Submit, Label => loc("Create") &>
+
+
+</&>
+</div>
+
+<div id="ticket-create-basics">
+<&| /Widgets/TitleBox &>
+   <input type="hidden" class="hidden" name="Queue" value="<%$QueueObj->id %>" />
+<%perl>
+
+$showrows->(
+
+   # loc('Queue') => $m->scomp( '/Ticket/Elements/ShowQueue', QueueObj => $QueueObj ) ,
+
+    loc('Status') =>
+
+        $m->scomp(
+        "/Elements/SelectStatus",
+        Name         => "Status",
+        Default      => $ARGS{Status} || 'new',
+        DefaultValue => 0,
+        SkipDeleted  => 1
+        ),
+
+    loc("Owner") =>
+
+        $m->scomp(
+        "/Elements/SelectOwner",
+        Name         => "Owner",
+        QueueObj     => $QueueObj,
+        Default      => $ARGS{Owner} || $RT::Nobody->Id,
+        DefaultValue => 0
+        ),
+
+    loc("Requestors") => $m->scomp(
+        "/Elements/EmailInput",
+        Name    => 'Requestors',
+        Size    => '40',
+        Default => $ARGS{Requestors} || $session{CurrentUser}->EmailAddress
+    ),
+
+    loc("Cc") =>
+
+        $m->scomp( "/Elements/EmailInput", Name => 'Cc', Size => '40', Default => $ARGS{Cc} )
+        . '<span class="comment"><i><font size="-2">'
+        . loc(
+        "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <strong>will</strong> receive future updates.)"
+        )
+        . '</font></i></span>',
+
+    loc("Admin Cc") =>
+
+        $m->scomp( "/Elements/EmailInput", Name => 'AdminCc', Size => '40', Default => $ARGS{AdminCc} )
+        . '<span class="comment" colspan="2"><i><font size="-2">'
+        . loc(
+        "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <strong>will</strong> receive future updates.)"
+        )
+        . '</font></i></span>',
+
+
+);
+
+
+$m->scomp("/Ticket/Elements/EditCustomFields", %ARGS, QueueObj => $QueueObj );
+
+
+$m->scomp("/Ticket/Elements/EditTransactionCustomFields", %ARGS, QueueObj => $QueueObj );
+
+</%perl>
+% if (exists $session{'Attachments'}) {
+
+<%loc("Attached file") %>
+
+<%loc("Check box to delete")%><br />
+% foreach my $attach_name (keys %{$session{'Attachments'}}) {
+<input type="checkbox" class="checkbox" name="DeleteAttach-<%$attach_name%>" value="1" /><%$attach_name%><br />
+% } # end of foreach
+
+
+% } # end of if
+
+<%perl>
+$showrows->(
+    loc("Attach file") =>
+
+        '<div class="value" colspan="5">
+<input type="file" name="Attach" />
+<input type="submit" class="button" name="AddMoreAttach" value="' . loc("Add More Files") . '" />'
+);
+</%perl>
+
+
+% if ( $gnupg_widget ) {
+%$m->scomp("/Elements/GnuPG/SignEncryptWidget", self => $gnupg_widget, QueueObj => $QueueObj )
+% }
+
+
+    <div class="ticket-info-basics">
+	  <&| /Widgets/TitleBox, title => loc('The Basics'), 
+		title_class=> 'inverse',  
+		color => "#993333" &>
+<%perl>
+$showrows->(
+    loc("Priority") => $m->scomp(
+        "/Elements/SelectPriority",
+        Name    => "InitialPriority",
+        Default => $ARGS{InitialPriority} ? $ARGS{InitialPriority} : $QueueObj->InitialPriority,
+    ),
+    loc("Final Priority") => $m->scomp(
+        "/Elements/SelectPriority",
+        Name    => "FinalPriority",
+        Default => $ARGS{FinalPriority} ? $ARGS{FinalPriority} : $QueueObj->FinalPriority,
+    ),
+
+    loc("Time Estimated") => '<span class="timefield">'.$m->scomp(
+        "/Elements/EditTimeValue",
+        Name    => 'TimeEstimated',
+        Default => $ARGS{TimeEstimated} || '',
+        InUnits => $ARGS{'TimeEstimated-TimeUnits'}
+        ).'</span>',
+
+    loc("Time Worked") => '<span class="timefield">'.$m->scomp(
+        "/Elements/EditTimeValue",
+        Name    => 'TimeWorked',
+        Default => $ARGS{TimeWorked} || '',
+        InUnits => $ARGS{'TimeWorked-TimeUnits'}
+    ). '</span>',
+
+    loc("Time Left") => '<span class="timefield">'.$m->scomp(
+        "/Elements/EditTimeValue",
+        Name    => 'TimeLeft',
+        Default => $ARGS{TimeLeft} || '',
+        InUnits => $ARGS{'TimeLeft-TimeUnits'}
+    ).'</span>',
+);
+
+</%perl>
+</&>
+<&|/Widgets/TitleBox, title => loc("Dates"),
+		title_class=> 'inverse',  
+		 color => "#663366"  &>
+
+<%perl>
+$showrows->(
+    loc("Starts") => $m->scomp( "/Elements/SelectDate", Name => "Starts", Default => ( $ARGS{Starts} || '' )),
+    loc("Due")    => $m->scomp( "/Elements/SelectDate", Name => "Due",    Default => ($ARGS{Due}    || '' ))
+);
+
+</%perl>
+</&>
+
+<&|/Widgets/TitleBox, title => loc('Links'), title_class=> 'inverse' &>
+
+<em><%loc("(Enter ticket ids or URLs, separated with spaces)")%></em>
+
+<%perl>
+$showrows->(
+    loc("Depends on")     => '<input size="10" name="new-DependsOn" value="' . ($ARGS{'new-DependsOn'} || '' ). '" />',
+    loc("Depended on by") => '<input size="10" name="DependsOn-new" value="' . ($ARGS{'DependsOn-new'} || '' ) . '" />',
+    loc("Parents")        => '<input size="10" name="new-MemberOf" value="' . ($ARGS{'new-MemberOf'} || '') . '" />',
+    loc("Children")       => '<input size="10" name="MemberOf-new" value="' . ($ARGS{'MemberOf-new'} || '') . '" />',
+    loc("Refers to")      => '<input size="10" name="new-RefersTo" value="' . ($ARGS{'new-RefersTo'} || '') . '" />',
+    loc("Referred to by") => '<input size="10" name="RefersTo-new" value="' . ($ARGS{'RefersTo-new'} || ''). '" />'
+);
+</%perl>
+
+</&>
+
+
+<& /Elements/Submit, Label => loc("Create") &>
+</form>
+</&>
+</&>
diff --git a/share/html/m/ticket/history b/share/html/m/ticket/history
new file mode 100644
index 0000000..a49945d
--- /dev/null
+++ b/share/html/m/ticket/history
@@ -0,0 +1,31 @@
+<%args>
+$id => undef
+</%args>
+<%init>
+my $t = RT::Ticket->new($session{CurrentUser});
+$t->Load($id);
+my $history = $t->Transactions()->ItemsArrayRef;
+</%init>
+<&| /m/_elements/wrapper, title => $t->Subject &>
+<div class="history">
+<& /m/_elements/ticket_menu, ticket => $t &>
+<&|/Widgets/TitleBox &>
+<ul class="history-list">
+% for my $entry (reverse @$history) {
+<li>
+<span class="age"><% $entry->CreatedObj->AgeAsString() %></span> -
+<& /Elements/ShowUser, User => $entry->CreatorObj &> - 
+<%$entry->BriefDescription%>
+% if ($entry->Type !~ /EmailRecord/) {
+% if ($entry->ContentObj) {
+<div class="txn-content">
+<%$entry->Content%>
+</div>
+%}
+% }
+</li>
+% }
+</ul>
+</&>
+</div>
+</&>
diff --git a/share/html/m/ticket/modify b/share/html/m/ticket/modify
new file mode 100644
index 0000000..e69de29
diff --git a/share/html/m/ticket/reply b/share/html/m/ticket/reply
new file mode 100644
index 0000000..34dc2c2
--- /dev/null
+++ b/share/html/m/ticket/reply
@@ -0,0 +1,171 @@
+<&|/m/_elements/wrapper, title => loc('Update ticket #[_1]', $t->id) &>
+<& /m/_elements/ticket_menu, ticket => $t &>
+<& /Elements/ListActions, actions => \@results &>
+<div class="ticket-reply">
+<&|/Widgets/TitleBox &>
+<form action="reply" id="update"
+    method="post" enctype="multipart/form-data">
+<input type="hidden" class="hidden" name="DefaultStatus" value="<% $DefaultStatus ||''%>" />
+<input type="hidden" class="hidden" name="Action" value="<% $ARGS{Action}||'' %>" />
+
+<div class="entry"><span class="label"><&|/l&>Status</&>:</span>
+<div class="value">
+<& /Elements/SelectStatus, Name=>"Status", DefaultLabel => loc("[_1] (Unchanged)", loc($t->Status)), Default => $ARGS{'Status'} || ($t->Status eq $DefaultStatus ? undef : $DefaultStatus)&>
+</div></div>
+
+<div class="entry"><span class="label"><&|/l&>Owner</&>:</span>
+<div class="value">
+<& /Elements/SelectOwner,
+    Name         => "Owner",
+    TicketObj    => $t,
+    QueueObj     => $t->QueueObj,
+    DefaultLabel => loc("[_1] (Unchanged)", $t->OwnerObj->Name),
+    Default      => $ARGS{'Owner'}
+&>
+</div></div>
+<div class="entry timefield"><span class="label"><&|/l&>Worked</&>:</span><span class="value">
+<& /Elements/EditTimeValue,
+    Name => 'UpdateTimeWorked',
+    Default => $ARGS{UpdateTimeWorked}||'',
+    InUnits => $ARGS{'UpdateTimeWorked-TimeUnits'}||'minutes',
+&>
+</span></div>
+<input type="hidden" class="hidden" name="id" value="<%$t->Id%>" /><br />
+<div class="entry"><span class="label"><&|/l&>Update Type</&>:</span>
+<div class="value"><select name="UpdateType">
+% if ($CanComment) {
+<option value="private" <% ($ARGS{'UpdateType'} &&  $ARGS{'UpdateType'} eq "private") ? qq[ selected="selected"] : !$ARGS{'UpdateType'}&&$CommentDefault |n %>><&|/l&>Comments (Not sent to requestors)</&></option>
+% }
+% if ($CanRespond) {
+<option value="response" <% ($ARGS{'UpdateType'} && $ARGS{'UpdateType'} eq "response") ? qq[ selected="selected"] : !$ARGS{'UpdateType'}&&$ResponseDefault |n %>><&|/l&>Reply to requestors</&></option>
+% }
+</select> 
+</div></div>
+<div class="entry"><span class="label"><&|/l&>Subject</&>:</span><div class="value"> <input name="UpdateSubject" size="60" value="<% $ARGS{UpdateSubject} || $t->Subject()%>" />
+% $m->callback( %ARGS, CallbackName => 'AfterSubject' );
+</div></div>
+
+<div class="entry"><span class="label"><&|/l&>One-time Cc</&>:</span><span class="value"><& /Elements/EmailInput, Name => 'UpdateCc', Size => '60', Default => $ARGS{UpdateCc} &></span></div>
+
+<div class="entry"><span class="label"><&|/l&>One-time Bcc</&>:</span><span class="value"><& /Elements/EmailInput, Name => 'UpdateBcc', Size => '60', Default => $ARGS{UpdateBcc} &></span></div>
+
+<div class="entry"><span class="label" ><&|/l&>Message</&>:</span><div class="value">
+% if (exists $ARGS{UpdateContent}) {
+% # preserve QuoteTransaction so we can use it to set up sane references/in/reply to
+% my $temp = $ARGS{'QuoteTransaction'};
+% delete $ARGS{'QuoteTransaction'};
+<& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0, %ARGS&>
+% $ARGS{'QuoteTransaction'} = $temp;
+% } else {
+% my $IncludeSignature = 1;
+% $IncludeSignature = 0 if $Action ne 'Respond' && !RT->Config->Get('MessageBoxIncludeSignatureOnComment');
+<& /Elements/MessageBox, Name=>"UpdateContent", IncludeSignature => $IncludeSignature, %ARGS &>
+% }
+</div></div>
+<& /Elements/Submit, Label => loc('Update Ticket'), Name => 'SubmitTicket' &>
+</form>
+</&>
+</div>
+</&>
+<%INIT>
+my $CanRespond = 0;
+my $CanComment = 0;
+my $checks_failure = 0;
+my $title;
+
+my $t = LoadTicket($id);
+
+my @results;
+
+$m->callback( Ticket => $t, ARGSRef => \%ARGS, results => \@results, CallbackName => 'Initial' );
+
+unless($DefaultStatus){
+    $DefaultStatus=($ARGS{'Status'} ||$t->Status());
+}
+
+if ($DefaultStatus eq 'new'){
+    $DefaultStatus='open';
+}
+
+if ($DefaultStatus eq 'resolved') {
+    $title = loc("Resolve ticket #[_1] ([_2])", $t->id, $t->Subject);
+} else {
+    $title = loc("Update ticket #[_1] ([_2])", $t->id, $t->Subject);
+}
+
+# Things needed in the template - we'll do the processing here, just
+# for the convenience:
+
+my ($CommentDefault, $ResponseDefault);
+if ($Action ne 'Respond') {
+    $CommentDefault = qq[ selected="selected"]; 
+    $ResponseDefault = "";
+} else {
+    $CommentDefault = ""; 
+    $ResponseDefault = qq[ selected="selected"];
+}
+
+
+
+$CanRespond = 1 if ( $t->CurrentUserHasRight('ReplyToTicket') or
+                     $t->CurrentUserHasRight('ModifyTicket') ); 
+
+$CanComment = 1 if ( $t->CurrentUserHasRight('CommentOnTicket') or
+                     $t->CurrentUserHasRight('ModifyTicket') ); 
+
+
+# {{{ deal with deleting uploaded attachments
+foreach my $key (keys %ARGS) {
+    if ($key =~ m/^DeleteAttach-(.+)$/) {
+        delete $session{'Attachments'}{$1};
+    }
+    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
+}
+# }}}
+
+# {{{ store the uploaded attachment in session
+if ($ARGS{'Attach'}) {            # attachment?
+    my $attachment = MakeMIMEEntity(
+        AttachmentFieldName => 'Attach'
+    );
+
+    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
+    $session{'Attachments'} = {
+        %{$session{'Attachments'} || {}},
+        $file_path => $attachment,
+    };
+}
+# }}}
+
+# delete temporary storage entry to make WebUI clean
+unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
+    delete $session{'Attachments'};
+}
+# }}}
+
+# check email addresses for RT's
+{
+    foreach my $field ( qw(UpdateCc UpdateBcc) ) {
+        my $value = $ARGS{ $field };
+        next unless defined $value && length $value;
+
+        my @emails = Email::Address->parse( $value );
+        foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) {
+            push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc(substr($field, 6)) );
+            $checks_failure = 1;
+            $email = undef;
+        }
+        $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails;
+    }
+}
+
+if ( !$checks_failure && exists $ARGS{SubmitTicket} ) {
+    return $m->comp('/m/ticket/show', TicketObj => $t, %ARGS);
+}
+</%INIT>
+
+<%ARGS>
+$id => undef
+$Action => undef
+$DefaultStatus => undef
+</%ARGS>
diff --git a/share/html/m/ticket/select_create_queue b/share/html/m/ticket/select_create_queue
new file mode 100644
index 0000000..88cf203
--- /dev/null
+++ b/share/html/m/ticket/select_create_queue
@@ -0,0 +1,18 @@
+<%init>
+my $queues = RT::Queues->new($session{'CurrentUser'});
+$queues->UnLimit();
+
+</%init>
+<&| /m/_elements/wrapper, title => loc("Create a ticket") &>
+<div class="select_queue">
+<&|/Widgets/TitleBox, title => loc("Select a queue") &>
+<ul class="menu">
+% while (my $q = $queues->Next()) {
+% next if (! $q->CurrentUserHasRight('CreateTicket'));
+<li><a href="<%RT->Config->Get('WebPath')%>/m/ticket/create?Queue=<%$q->id%>"><%$q->Name%></a></li>
+% }
+</ul>
+</&>
+</div>
+</&>
+
diff --git a/share/html/m/ticket/show b/share/html/m/ticket/show
new file mode 100644
index 0000000..c650019
--- /dev/null
+++ b/share/html/m/ticket/show
@@ -0,0 +1,431 @@
+<%args>
+$id => undef
+</%args>
+<%init>
+my $Ticket;
+my @Actions; 
+
+if ($ARGS{'id'} eq 'new') {
+    # {{{ Create a new ticket
+
+    my $Queue = new RT::Queue( $session{'CurrentUser'} );
+    $Queue->Load($ARGS{'Queue'});
+    unless ( $Queue->id ) {
+        Abort('Queue not found');
+    }
+
+    unless ( $Queue->CurrentUserHasRight('CreateTicket') ) {
+        Abort('You have no permission to create tickets in that queue.');
+    }
+
+    ($Ticket, @Actions) = CreateTicket(
+        Attachments => delete $session{'Attachments'},
+        %ARGS,
+    );
+    unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
+        Abort("No permission to view newly created ticket #".$Ticket->id.".");
+    }
+    # }}}
+} else { 
+    $Ticket ||= LoadTicket($ARGS{'id'});
+
+    $m->callback( CallbackName => 'BeforeProcessArguments',
+        TicketObj => $Ticket,
+        ActionsRef => \@Actions, ARGSRef => \%ARGS );
+    if ( defined $ARGS{'Action'} ) {
+        if ($ARGS{'Action'} =~ /^(Steal|Kill|Take|SetTold)$/) {
+            my $action = $1;
+            my ($res, $msg) = $Ticket->$action();
+            push(@Actions, $msg);
+        }
+    }
+
+    $m->callback(CallbackName => 'ProcessArguments', 
+            Ticket => $Ticket, 
+            ARGSRef => \%ARGS, 
+            Actions => \@Actions);
+    
+    $ARGS{UpdateAttachments} = $session{'Attachments'};
+    push @Actions,
+        ProcessUpdateMessage(
+        ARGSRef   => \%ARGS,
+        Actions   => \@Actions,
+        TicketObj => $Ticket,
+        );
+    delete $session{'Attachments'};
+
+    #Process status updates
+    push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $Ticket );
+    push @Actions, ProcessTicketBasics(  ARGSRef => \%ARGS, TicketObj => $Ticket );
+    push @Actions, ProcessTicketLinks(   ARGSRef => \%ARGS, TicketObj => $Ticket );
+    push @Actions, ProcessTicketDates(   ARGSRef => \%ARGS, TicketObj => $Ticket );
+    push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, TicketObj => $Ticket );
+
+    # XXX: we shouldn't block actions here if user has no right to see the ticket,
+    # but we should allow him to see actions he has done
+    unless ($Ticket->CurrentUserHasRight('ShowTicket')) {
+        Abort("No permission to view ticket");
+    }
+    if ( $ARGS{'MarkAsSeen'} ) {
+        $Ticket->SetAttribute(
+            Name => 'User-'. $Ticket->CurrentUser->id .'-SeenUpTo',
+            Content => $Ticket->LastUpdated,
+        );
+        push @Actions, loc('Marked all messages as seen');
+    }
+}
+
+$m->callback(
+    CallbackName => 'BeforeDisplay',
+    TicketObj => \$Ticket,
+    Actions => \@Actions,
+    ARGSRef => \%ARGS,
+);
+
+# This code does automatic redirection if any updates happen. 
+
+if (@Actions) {
+
+    # We've done something, so we need to clear the decks to avoid
+    # resubmission on refresh.
+    # But we need to store Actions somewhere too, so we don't lose them.
+    my $key = Digest::MD5::md5_hex( rand(1024) );
+    push @{ $session{"Actions"}->{$key} ||= [] }, @Actions;
+    $session{'i'}++;
+    my $url = RT->Config->Get('WebURL') . "/m/ticket/show?id=" . $Ticket->id . "&results=" . $key;
+    $url .= '#' . $ARGS{Anchor} if $ARGS{Anchor};
+    RT::Interface::Web::Redirect($url);
+}
+
+# If we haven't been passed in an Attachments object (through the precaching mechanism)
+# then we need to find one
+my $Attachments = $m->comp('/Ticket/Elements/FindAttachments', Ticket => $Ticket);
+
+my %documents;
+while ( my $attach = $Attachments->Next() ) {
+    next unless ($attach->Filename());
+   unshift( @{ $documents{ $attach->Filename } }, $attach );
+}
+
+my $CustomFields = $Ticket->CustomFields;
+$m->callback(
+    CallbackName => 'MassageCustomFields',
+    Object => $Ticket,
+    CustomFields => $CustomFields,
+);
+
+my $print_value = sub {
+    my ($cf, $value) = @_;
+    my $linked = $value->LinkValueTo;
+    if ( defined $linked && length $linked ) {
+        my $linked = $m->interp->apply_escapes( $linked, 'h' );
+        $m->out('<a href="'. $linked .'" target="_new">');
+    }
+    my $comp = "ShowCustomField". $cf->Type;
+    $m->callback(
+        CallbackName => 'ShowComponentName',
+        Name         => \$comp,
+        CustomField  => $cf,
+        Object       => $Ticket,
+    );
+    if ( $m->comp_exists( $comp ) ) {
+        $m->comp( $comp, Object => $value );
+    } else {
+        $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) );
+    }
+    $m->out('</a>') if defined $linked && length $linked;
+
+    # This section automatically populates a<div with the "IncludeContentForValue" for this custom
+    # field if it's been defined
+    if ( $cf->IncludeContentForValue ) {
+       my $vid = $value->id;
+       $m->out(   '<div class="object_cf_value_include" id="object_cf_value_'. $vid .'">' );
+       $m->print( loc("See also:") );
+       $m->out(   '<a href="'. $value->IncludeContentForValue .'">' );
+       $m->print( $value->IncludeContentForValue );
+       $m->out(   qq{</a></div>\n} );
+       $m->out(   qq{<script><!--\nahah('} );
+       $m->print( $value->IncludeContentForValue );
+       $m->out(   qq{', 'object_cf_value_$vid');\n--></script>\n} );
+    }
+};
+
+</%init>
+<&| /m/_elements/wrapper, title => $Ticket->Subject &>
+<div id="ticket-show">
+<& /m/_elements/ticket_menu, ticket => $Ticket &>
+
+    <&| /Widgets/TitleBox, title => loc('The Basics'),
+        class => 'ticket-info-basics',
+    &>
+
+
+ <div class="entry">
+    <div class="label id"><&|/l&>Id</&>:</div>
+    <div class="value id"><%$Ticket->Id %></div>
+  </div>
+ <div class="entry">
+    <div class="label status"><&|/l&>Status</&>:</div>
+    <div class="value status"><% loc($Ticket->Status) %></div>
+  </div>
+% if ($Ticket->TimeEstimated) {
+ <div class="entry">
+    <div class="label time estimated"><&|/l&>Estimated</&>:</div>
+    <div class="value time estimated"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeEstimated &></div>
+  </div>
+% }
+% if ($Ticket->TimeWorked) {
+ <div class="entry">
+    <div class="label time worked"><&|/l&>Worked</&>:</div>
+    <div class="value time worked"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeWorked &></div>
+  </div>
+% }
+% if ($Ticket->TimeLeft) {
+ <div class="entry">
+    <div class="label time left"><&|/l&>Left</&>:</div>
+    <div class="value time left"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeLeft &></div>
+  </div>
+% }
+ <div class="entry">
+    <div class="label priority"><&|/l&>Priority</&>:</div>
+    <div class="value priority"><& /Ticket/Elements/ShowPriority, Ticket => $Ticket &></div>
+  </div>
+ <div class="entry">
+    <div class="label queue"><&|/l&>Queue</&>:</div>
+    <div class="value queue"><& /Ticket/Elements/ShowQueue, QueueObj => $Ticket->QueueObj &></div>
+  </div>
+    </&>
+
+% if ($Ticket->CustomFields->First) {
+    <&| /Widgets/TitleBox, title => loc('Custom Fields'),
+        class => 'ticket-info-cfs',
+    &>
+
+% while ( my $CustomField = $CustomFields->Next ) {
+% my $Values = $Ticket->CustomFieldValues( $CustomField->Id );
+% my $count = $Values->Count;
+  <div class="entry" id="CF-<%$CustomField->id%>-ShowRow">
+    <div class="label"><% $CustomField->Name %>:</div>
+    <div class="value">
+% unless ( $count ) {
+<i><&|/l&>(no value)</&></i>
+% } elsif ( $count == 1 ) {
+%   $print_value->( $CustomField, $Values->First );
+% } else {
+<ul>
+% while ( my $Value = $Values->Next ) {
+<li>
+% $print_value->( $CustomField, $Value );
+</li>
+% }
+</ul>
+% }
+    </div>
+  </div>
+% }
+
+</&>
+% }
+
+    <&| /Widgets/TitleBox, title => loc('People'), class => 'ticket-info-people' &>
+
+
+ <div class="entry">
+    <div class="label"><&|/l&>Owner</&>:</div>
+    <div class="value"><& /Elements/ShowUser, User => $Ticket->OwnerObj, Ticket => $Ticket &>
+    </div>
+  </div>
+ <div class="entry">
+    <div class="label"><&|/l&>Requestors</&>:</div>
+    <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Requestors, Ticket => $Ticket &></div>
+  </div>
+ <div class="entry">
+    <div class="label"><&|/l&>Cc</&>:</div>
+    <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Cc, Ticket => $Ticket &></div>
+  </div>
+ <div class="entry">
+    <div class="label"><&|/l&>AdminCc</&>:</div>
+    <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket &></div>
+  </div>
+
+    </&>
+
+% if (keys %documents) {
+<&| /Widgets/TitleBox, title => loc('Attachments'), 
+        title_class=> 'inverse',  
+        class => 'ticket-info-attachments',
+        color => "#336699" &>
+
+% foreach my $key (keys %documents) {
+
+<%$key%><br />
+<ul>
+% foreach my $rev (@{$documents{$key}}) {
+
+<%PERL>
+my $size = $rev->ContentLength;
+
+if ($size) {
+    my $kb = int($size/102.4) / 10;
+    my $units = RT->Config->Get('AttachmentUnits');
+
+    if (!defined($units)) {
+        if ($size > 1024) {
+            $size = $kb . "k";
+        }
+        else {
+            $size = $size . "b";
+        }
+    }
+    elsif ($units eq 'k') {
+        $size = $kb . "k";
+    }
+    else {
+        $size = $size . "b";
+    }
+
+</%PERL>
+
+<li><font size="-2">
+<a href="<%RT->Config->Get('WebPath')%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | u%>">
+<&|/l, $rev->CreatedAsString, $size, $rev->CreatorObj->Name &>[_1] ([_2]) by [_3]</&>
+</a>
+</font></li>
+% }
+% }
+</ul>
+
+% }
+</&>
+
+% }
+% # too painful to deal with reminders
+% if ( 0 &&  RT->Config->Get('EnableReminders') ) {
+    <&|/Widgets/TitleBox, title => loc("Reminders"),
+        class => 'ticket-info-reminders',
+    &>
+       <div class="entry"><div
+            <form action="<%RT->Config->Get('WebPath')%>/Ticket/Display.html" method="post">
+                <& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 0 &>
+                <div align="right"><input type="submit" class="button" value="<&|/l&>Save</&>" /></div>
+            </form>
+        </div></div>
+    </&>
+% }
+
+    <&| /Widgets/TitleBox, title => loc("Dates"),
+        class => 'ticket-info-dates',
+    &>
+
+
+ <div class="entry">
+    <div class="label date created"><&|/l&>Created</&>:</div>
+    <div class="value date created"><% $Ticket->CreatedObj->AsString %></div>
+  </div>
+ <div class="entry">
+    <div class="label date starts"><&|/l&>Starts</&>:</div>
+    <div class="value date starts"><% $Ticket->StartsObj->AsString %></div>
+  </div>
+ <div class="entry">
+    <div class="label date started"><&|/l&>Started</&>:</div>
+    <div class="value date started"><% $Ticket->StartedObj->AsString %></div>
+  </div>
+ <div class="entry">
+    <div class="label date told"><&|/l&>Last Contact</&>:</div>
+    <div class="value date told"><% $Ticket->ToldObj->AsString %></div>
+  </div>
+ <div class="entry">
+    <div class="label date due"><&|/l&>Due</&>:</div>
+% my $due = $Ticket->DueObj;
+% if ( $due && $due->Unix > 0 && $due->Diff < 0 ) {
+    <div class="value date due"><span class="overdue"><% $due->AsString  %></span></div>
+% } else {
+    <div class="value date due"><% $due->AsString  %></div>
+% }
+  </div>
+ <div class="entry">
+    <div class="label date resolved"><&|/l&>Closed</&>:</div>
+    <div class="value date resolved"><% $Ticket->ResolvedObj->AsString  %></div>
+  </div>
+ <div class="entry">
+    <div class="label date updated"><&|/l&>Updated</&>:</div>
+% my $UpdatedString = $Ticket->LastUpdated ? loc("[_1] by [_2]", $Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name) : loc("Never");
+    <div class="value date updated"><% $UpdatedString | h %></div>
+  </div>
+
+    </&>
+
+    <&| /Widgets/TitleBox, title => loc('Links'), class => 'ticket-info-links' &>
+
+ <div class="entry">
+    <div class="label"><% loc('Depends on')%>:</div>
+    <div class="value">
+
+<%PERL>
+my ( @active, @inactive, @not_tickets );
+for my $link ( @{ $Ticket->DependsOn->ItemsArrayRef } ) {
+    my $target = $link->TargetObj;
+    if ( $target && $target->isa('RT::Ticket') ) {
+        if ( $target->QueueObj->IsInactiveStatus( $target->Status ) ) {
+            push( @inactive, $link->TargetURI );
+        }
+        else {
+            push( @active, $link->TargetURI );
+        }
+    }
+    else {
+        push( @not_tickets, $link->TargetURI );
+    }
+}
+</%PERL>
+
+
+<ul>
+% for my $Link (@not_tickets, @active, @inactive) {
+<li><& /Elements/ShowLink, URI => $Link &></li>
+% }
+</ul>
+    </div>
+  </div>
+ <div class="entry">
+    <div class="label"><% loc('Depended on by')%>:</div>
+    <div class="value">
+<ul>
+% while (my $Link = $Ticket->DependedOnBy->Next) {
+<li><& /Elements/ShowLink, URI => $Link->BaseURI &></li>
+% }
+</ul>
+    </div>
+  </div>
+ <div class="entry">
+    <div class="label"><% loc('Parents') %>:</div>
+    <div class="value"><& /Ticket/Elements/ShowParents, Ticket => $Ticket &></div>
+  </div>
+ <div class="entry">
+    <div class="label"><% loc('Children')%>:</div>
+    <div class="value"><& /Ticket/Elements/ShowMembers, Ticket => $Ticket &></div>
+  </div>
+ <div class="entry">
+    <div class="label"><% loc('Refers to')%>:</div>
+    <div class="value">
+<ul>
+% while (my $Link = $Ticket->RefersTo->Next) {
+<li><& /Elements/ShowLink, URI => $Link->TargetURI &></li>
+% }
+</ul>
+    </div>
+  </div>
+ <div class="entry">
+    <div class="label"><% loc('Referred to by')%>:</div>
+    <div class="value">
+    <ul>
+% while (my $Link = $Ticket->ReferredToBy->Next) {
+% next if (UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket')  && $Link->BaseObj->Type eq 'reminder');
+<li><& /Elements/ShowLink, URI => $Link->BaseURI &></li>
+% }
+</ul>
+    </div>
+  </div>
+    </&>
+</div>
+</&>
diff --git a/share/html/m/tickets/requested b/share/html/m/tickets/requested
new file mode 100644
index 0000000..3043e05
--- /dev/null
+++ b/share/html/m/tickets/requested
@@ -0,0 +1,4 @@
+<%init>
+ $m->comp('../_elements/ticket_list', %ARGS, query => 'Requestors.EmailAddress = "'.$session{CurrentUser}->EmailAddress.'" AND (Status != "resolved" AND Status != "rejected" AND Status != "stalled")'); 
+$m->abort();
+</%init>
diff --git a/share/html/m/tickets/search b/share/html/m/tickets/search
new file mode 100644
index 0000000..16864b4
--- /dev/null
+++ b/share/html/m/tickets/search
@@ -0,0 +1,64 @@
+<%args>
+$page => 1
+$order_by => 'id'
+$order => 'desc'
+$name => undef
+</%args>
+<%init>
+use RT::Search::Googleish;
+my $query = $ARGS{'query'};
+if ($ARGS{'q'}) {
+    my $tickets = RT::Tickets->new( $session{'CurrentUser'} );
+    my %args = (
+        Argument   => $ARGS{q},
+        TicketsObj => $tickets,
+    );
+    my $search = RT::Search::Googleish->new(%args);
+    $query = $search->QueryToSQL();
+
+}
+
+elsif ($ARGS{'name'}) {
+my $search_arg;
+
+my $search;
+
+    if ($name)  {
+        ($search) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named( 'Search - ' . $name );
+        unless ( $search && $search->Id ) {
+            my (@custom_searches) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('SavedSearch');
+            foreach my $custom (@custom_searches) {
+                if ( $custom->Description eq $name ) { $search = $custom; last }
+            }
+            unless ( $search && $search->id ) {
+                $m->out("Predefined search $name not found");
+                return;
+            }
+        }
+
+        $search_arg = $session{'CurrentUser'}->UserObj->Preferences( $search, $search->Content );
+    }
+
+    foreach ($search_arg) {
+        if ( $_->{'Query'} =~ /__Bookmarks__/ ) {
+            $_->{'Rows'} = 999;
+
+            # DEPRECATED: will be here for a while up to 3.10/4.0
+            my $bookmarks = $session{'CurrentUser'}->UserObj->FirstAttribute('Bookmarks');
+            $bookmarks = $bookmarks->Content if $bookmarks;
+            $bookmarks ||= {};
+            my $query = join( " OR ", map " id = '$_' ", grep $bookmarks->{$_}, keys %$bookmarks ) || 'id=0';
+            $_->{'Query'} =~ s/__Bookmarks__/( $query )/g;
+        }
+    }
+
+    $query    = $search_arg->{Query};
+    $order_by = $search_arg->{OrderBy};
+    $order    = $search_arg->{Order};
+
+}
+
+
+$m->comp('../_elements/ticket_list', query => $query, page => $page, order_by => $order_by, order => $order);
+$m->abort();
+</%init>

commit 8747ab26c6eb349bd210ba7ac68e7d653bb864a9
Merge: d885f6b df8d7d0
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Mon Sep 6 14:13:56 2010 -0400

    Merge branch 'integrate-mobile-ui' into 3.9-trunk
    
    * integrate-mobile-ui:
      Initial import of the mobile UI extension as a core feature


commit a7f761ca40657ecec634ca32c9766be8fab3252e
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Mon Sep 6 14:14:23 2010 -0400

    Added S60 as a mobile useragent

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index a5a5df8..a946176 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -662,7 +662,7 @@ sub MobileClient {
     my $self = shift;
 
 
-if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS)/io && !$HTML::Mason::Commands::session{'NotMobile'})  {
+if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS|S60)/io && !$HTML::Mason::Commands::session{'NotMobile'})  {
     return 1;
 } else {
     return undef;

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


More information about the Rt-commit mailing list