[Rt-commit] rt branch, 4.6/article-refactor, created. rt-4.4.2-104-g4f3ed5dce

Jim Brandt jbrandt at bestpractical.com
Sun Jun 24 16:02:13 EDT 2018


The branch, 4.6/article-refactor has been created
        at  4f3ed5dce9505b5ab5fa0ba8dce2acd00517c1f5 (commit)

- Log -----------------------------------------------------------------
commit 0e2c890473c53b26ac64f37463af95c852e4b087
Author: craig Kaiser <craig at bestpractical.com>
Date:   Wed Feb 21 14:35:54 2018 -0500

    Add default value for Articles in Queues
    
    Automatically add default article content to tickets on create for queue.

diff --git a/lib/RT/Queue.pm b/lib/RT/Queue.pm
index 4c2f5fb62..aa6131a0b 100644
--- a/lib/RT/Queue.pm
+++ b/lib/RT/Queue.pm
@@ -1185,6 +1185,29 @@ sub SetDefaultValue {
         $new_value = $self->loc( '(no value)' );
     }
 
+    if ( $args{Name} eq 'Article' && $args{Value} ) {
+        my $article = RT::Article->new($self->CurrentUser);
+        my ($ret, $msg);
+        if ( $args{Value} =~ /^\d+$/ ) {
+            ($ret, $msg) = $article->Load( $args{Value} );
+        }
+        else {
+            ($ret, $msg) = $article->LoadByCols( Name => $args{Value} );
+        }
+        return ($ret, $msg ) unless $ret;
+
+        $args{Value} = $article->Id;
+        $new_value = $article->Name;
+    }
+
+    if ( $args{Name} eq 'Article' && $old_value =~ /^\d+$/ ) {
+        my $article = RT::Article->new($self->CurrentUser);
+        my ($ret, $msg) = $article->Load( $old_value );
+        if ($ret) {
+            $old_value = $article->Name;
+        }
+    }
+
     return 1 if $new_value eq $old_value;
 
     my ($ret, $msg) = $self->SetAttribute(
diff --git a/share/html/Admin/Queues/DefaultValues.html b/share/html/Admin/Queues/DefaultValues.html
index aff93cc0b..331c97c00 100644
--- a/share/html/Admin/Queues/DefaultValues.html
+++ b/share/html/Admin/Queues/DefaultValues.html
@@ -71,6 +71,11 @@
         Grouping => 'Basics',
         InTable => 1,
     &>
+
+    <tr><td class="label"><&|/l&>Article</&>:</td>
+        <td><& /Elements/SelectArticle, QueueObj => $queue, DefaultArticle => $queue->DefaultValue('Article') &>
+    </td></tr>
+
     </table>
     </&>
 </div>
@@ -149,7 +154,7 @@ if ( $ARGS{Reset} ) {
     }
 }
 elsif ( $ARGS{Update} ) {
-    for my $field ( qw/InitialPriority FinalPriority Starts Due/ ) {
+    for my $field ( qw/InitialPriority FinalPriority Starts Due Article/ ) {
         my ($ret, $msg) = $queue->SetDefaultValue(
             Name => $field,
             Value => $ARGS{$field},
diff --git a/share/html/Articles/Elements/IncludeArticle b/share/html/Articles/Elements/IncludeArticle
index 5307875cf..0f4becbe5 100644
--- a/share/html/Articles/Elements/IncludeArticle
+++ b/share/html/Articles/Elements/IncludeArticle
@@ -48,6 +48,7 @@
 <%INIT>
 
 my $parent_args = $m->caller_args(-1);
+my @articles;
 
 my $name_prefix = '';
 $name_prefix = $ARGS{'Name'} .'-'
@@ -55,18 +56,18 @@ $name_prefix = $ARGS{'Name'} .'-'
     && grep rindex($_, "$ARGS{'Name'}-Articles-", 0) == 0,
         keys %$parent_args;
 
+my $Ticket = $ARGS{Ticket};
+if ( !$Ticket and $parent_args->{id} and $parent_args->{id} ne 'new' ) {
+    $Ticket = RT::Ticket->new($session{'CurrentUser'});
+    $Ticket->Load($parent_args->{id});
+    unless ( $Ticket->id ) {
+        $RT::Logger->error("Couldn't load ticket ". $parent_args->{id} )
+    }
+}
+
 foreach my $arg ( keys %$parent_args ) {
     next if $name_prefix && substr($arg, 0, length($name_prefix)) ne $name_prefix;
 
-    my $Ticket = $ARGS{Ticket};
-    if ( !$Ticket and $parent_args->{id} and $parent_args->{id} ne 'new' ) {
-        $Ticket = RT::Ticket->new($session{'CurrentUser'});
-        $Ticket->Load($parent_args->{id});
-        unless ( $Ticket->id ) {
-            $RT::Logger->error("Couldn't load ticket ". $parent_args->{id} )
-        }
-    }
-
     my $Queue = RT::Queue->new($session{CurrentUser});
     if ($Ticket && $Ticket->Id) {
         $Queue = $Ticket->QueueObj;
@@ -79,7 +80,26 @@ foreach my $arg ( keys %$parent_args ) {
         Queue => $Queue->Id,
     );
     next unless $article && $article->id;
+    push (@articles, $article);
+}
+
+if ( !@articles && ( !$parent_args->{id} || $parent_args->{id} eq 'new' ) && $parent_args->{'Queue'} ) {
+    my $queue_id = $parent_args->{'Queue'};
+    my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+    my ($ret, $msg) = $QueueObj->Load( $queue_id );
+    $RT::Logger->error($msg) unless ($ret);
+
+    if ( $QueueObj->id && $QueueObj->DefaultValue('Article') ) {
+        my $article = RT::Article->new($session{'CurrentUser'});
+        my ($ret, $msg) = $article->Load( $QueueObj->DefaultValue('Article') );
+        $RT::Logger->error($msg) unless ($ret);
+        if ($ret) {
+            push (@articles, $article);
+        }
+    }
+}
 
+foreach my $article (@articles) {
     my $formatted_article = $m->scomp('/Articles/Article/Elements/Preformatted',
         Article => $article, Ticket => $Ticket
     );
diff --git a/share/html/Articles/Elements/IncludeArticle b/share/html/Elements/SelectArticle
similarity index 56%
copy from share/html/Articles/Elements/IncludeArticle
copy to share/html/Elements/SelectArticle
index 5307875cf..62882e633 100644
--- a/share/html/Articles/Elements/IncludeArticle
+++ b/share/html/Elements/SelectArticle
@@ -45,55 +45,42 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<%INIT>
-
-my $parent_args = $m->caller_args(-1);
-
-my $name_prefix = '';
-$name_prefix = $ARGS{'Name'} .'-'
-    if $ARGS{'Name'}
-    && grep rindex($_, "$ARGS{'Name'}-Articles-", 0) == 0,
-        keys %$parent_args;
 
-foreach my $arg ( keys %$parent_args ) {
-    next if $name_prefix && substr($arg, 0, length($name_prefix)) ne $name_prefix;
-
-    my $Ticket = $ARGS{Ticket};
-    if ( !$Ticket and $parent_args->{id} and $parent_args->{id} ne 'new' ) {
-        $Ticket = RT::Ticket->new($session{'CurrentUser'});
-        $Ticket->Load($parent_args->{id});
-        unless ( $Ticket->id ) {
-            $RT::Logger->error("Couldn't load ticket ". $parent_args->{id} )
-        }
-    }
+<select name="<% $Name %>">
+<option value="">-</option>
+% while (my $article = $articles->Next) {
+<option <% ( $article->Name eq $DefaultArticle) ? qq[ selected="selected"] : '' |n %>
+    value="<%$article->Id%>"
+><% $article->Name %></option>
+% }
+</select>
 
-    my $Queue = RT::Queue->new($session{CurrentUser});
-    if ($Ticket && $Ticket->Id) {
-        $Queue = $Ticket->QueueObj;
-    }
+<%INIT>
+my $articles = RT::Articles->new( $session{'CurrentUser'} );
+$articles->LimitAppliedClasses( Queue => $QueueObj );
 
-    my $article = RT::Article->new($session{'CurrentUser'});
-    $article->LoadByInclude(
-        Field => substr($arg, length($name_prefix)),
-        Value => $parent_args->{$arg},
-        Queue => $Queue->Id,
-    );
-    next unless $article && $article->id;
+my $dropdown_limit = RT->Config->Get( 'DropdownMenuLimit' ) || 50;
+$m->callback( CallbackName => 'ModifyDropdownLimit', DropdownLimit => \$dropdown_limit );
 
-    my $formatted_article = $m->scomp('/Articles/Article/Elements/Preformatted',
-        Article => $article, Ticket => $Ticket
-    );
+my $autocomplete =  $articles->Count > $dropdown_limit ? 1 : 0;
 
-    $m->callback( Article => $article, Ticket => $Ticket, formatted_article => \$formatted_article );
+if ( $DefaultArticle and $DefaultArticle =~ /^\d+$/ ){
+    # We got an id, look up the name
+    my $default_article = RT::Article->new($session{'CurrentUser'});
+    my ($ret, $msg) = $default_article->Load( $DefaultArticle );
 
-    if (RT->Config->Get('MessageBoxRichText',  $session{'CurrentUser'})) {
-        $formatted_article =~ s/>/>/g;
-        $formatted_article =~ s/</</g;
-        $formatted_article =~ s/&/&/g;
-        $formatted_article =~ s/\n/\n<br \/>/g;
+    if ($ret) {
+         $DefaultArticle = $default_article->Name;
+    }
+    else {
+        RT::Logger->error("Unable to load article $DefaultArticle: $msg");
+        $DefaultArticle = '';
     }
-    $m->print($formatted_article);
-
 }
-return;
 </%INIT>
+
+<%ARGS>
+$QueueObj
+$ArticleSelectName => 'Article'
+$DefaultArticle => ''
+</%ARGS>

commit dd0f7cb14b56b28cb88416f6cb0dce21ac5b8947
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Sun Jun 24 12:14:00 2018 -0400

    Add autocomplete for articles

diff --git a/share/html/Elements/SelectArticle b/share/html/Elements/SelectArticle
index 62882e633..754f2aabb 100644
--- a/share/html/Elements/SelectArticle
+++ b/share/html/Elements/SelectArticle
@@ -46,6 +46,9 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 
+% if ( $autocomplete ) {
+<& "SelectArticleAutocomplete", QueueObj => $QueueObj, Default => $DefaultArticle, Name => $ArticleSelectName &>
+% } else {
 <select name="<% $Name %>">
 <option value="">-</option>
 % while (my $article = $articles->Next) {
@@ -54,6 +57,7 @@
 ><% $article->Name %></option>
 % }
 </select>
+% }
 
 <%INIT>
 my $articles = RT::Articles->new( $session{'CurrentUser'} );
diff --git a/share/html/Elements/SelectArticle b/share/html/Elements/SelectArticleAutocomplete
similarity index 65%
copy from share/html/Elements/SelectArticle
copy to share/html/Elements/SelectArticleAutocomplete
index 62882e633..18ada32fa 100644
--- a/share/html/Elements/SelectArticle
+++ b/share/html/Elements/SelectArticleAutocomplete
@@ -45,42 +45,10 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-
-<select name="<% $Name %>">
-<option value="">-</option>
-% while (my $article = $articles->Next) {
-<option <% ( $article->Name eq $DefaultArticle) ? qq[ selected="selected"] : '' |n %>
-    value="<%$article->Id%>"
-><% $article->Name %></option>
-% }
-</select>
-
-<%INIT>
-my $articles = RT::Articles->new( $session{'CurrentUser'} );
-$articles->LimitAppliedClasses( Queue => $QueueObj );
-
-my $dropdown_limit = RT->Config->Get( 'DropdownMenuLimit' ) || 50;
-$m->callback( CallbackName => 'ModifyDropdownLimit', DropdownLimit => \$dropdown_limit );
-
-my $autocomplete =  $articles->Count > $dropdown_limit ? 1 : 0;
-
-if ( $DefaultArticle and $DefaultArticle =~ /^\d+$/ ){
-    # We got an id, look up the name
-    my $default_article = RT::Article->new($session{'CurrentUser'});
-    my ($ret, $msg) = $default_article->Load( $DefaultArticle );
-
-    if ($ret) {
-         $DefaultArticle = $default_article->Name;
-    }
-    else {
-        RT::Logger->error("Unable to load article $DefaultArticle: $msg");
-        $DefaultArticle = '';
-    }
-}
-</%INIT>
+<input data-autocomplete="Articles" <% $QueueObj && $QueueObj->id ? q{data-autocomplete-queue="} . $QueueObj->id . q{"} : '' |n %> data-autocomplete-return="Name" name="<% $Name %>" value="<% $Default %>">
 
 <%ARGS>
-$QueueObj
-$ArticleSelectName => 'Article'
-$DefaultArticle => ''
+$QueueObj => undef
+$Default => undef
+$Name => 'Article'
 </%ARGS>
diff --git a/share/html/Elements/SelectArticle b/share/html/Helpers/Autocomplete/Articles
similarity index 63%
copy from share/html/Elements/SelectArticle
copy to share/html/Helpers/Autocomplete/Articles
index 62882e633..d03a2f2a0 100644
--- a/share/html/Elements/SelectArticle
+++ b/share/html/Helpers/Autocomplete/Articles
@@ -45,42 +45,50 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
+% $r->content_type('application/json; charset=utf-8');
+<% JSON( \@suggestions ) |n %>
+% $m->abort;
+<%ARGS>
+$term       => undef
+$max        => 10
+$op         => 'STARTSWITH'
+$right      => undef
+$return     => 'Name'
+$queue      => undef
+</%ARGS>
+<%INIT>
+# Only allow certain return fields
+$return = 'Name'
+    unless $return =~ /^(?:id|Name)$/;
 
-<select name="<% $Name %>">
-<option value="">-</option>
-% while (my $article = $articles->Next) {
-<option <% ( $article->Name eq $DefaultArticle) ? qq[ selected="selected"] : '' |n %>
-    value="<%$article->Id%>"
-><% $article->Name %></option>
-% }
-</select>
+$m->abort unless defined $return
+             and defined $term
+             and length $term;
 
-<%INIT>
-my $articles = RT::Articles->new( $session{'CurrentUser'} );
-$articles->LimitAppliedClasses( Queue => $QueueObj );
+# Sanity check the operator
+$op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i;
 
-my $dropdown_limit = RT->Config->Get( 'DropdownMenuLimit' ) || 50;
-$m->callback( CallbackName => 'ModifyDropdownLimit', DropdownLimit => \$dropdown_limit );
+$m->callback( CallbackName => 'ModifyMaxResults', max => \$max );
 
-my $autocomplete =  $articles->Count > $dropdown_limit ? 1 : 0;
+my $articles = RT::Articles->new( $session{CurrentUser} );
+if( $queue ) {
+    $articles->LimitAppliedClasses( Queue => $queue );
+}
 
-if ( $DefaultArticle and $DefaultArticle =~ /^\d+$/ ){
-    # We got an id, look up the name
-    my $default_article = RT::Article->new($session{'CurrentUser'});
-    my ($ret, $msg) = $default_article->Load( $DefaultArticle );
+$articles->RowsPerPage( $max );
+$articles->Limit(
+    FIELD           => 'Name',
+    OPERATOR        => $op,
+    VALUE           => $term,
+    ENTRYAGGREGATOR => 'OR',
+    CASESENSITIVE   => 0,
+);
 
-    if ($ret) {
-         $DefaultArticle = $default_article->Name;
-    }
-    else {
-        RT::Logger->error("Unable to load article $DefaultArticle: $msg");
-        $DefaultArticle = '';
-    }
+my @suggestions;
+while (my $a = $articles->Next) {
+    next if $right and not $a->CurrentUserHasRight($right);
+    my $value = $a->$return;
+    push @suggestions, { label => $a->Name, value => $value };
+    $m->callback( CallbackName => "ModifySuggestion", suggestions => @suggestions, label => $a );
 }
 </%INIT>
-
-<%ARGS>
-$QueueObj
-$ArticleSelectName => 'Article'
-$DefaultArticle => ''
-</%ARGS>
diff --git a/share/static/js/autocomplete.js b/share/static/js/autocomplete.js
index cd8ab2b0d..c4362edb1 100644
--- a/share/static/js/autocomplete.js
+++ b/share/static/js/autocomplete.js
@@ -5,7 +5,8 @@ window.RT.Autocomplete.Classes = {
     Users: 'user',
     Groups: 'group',
     Tickets: 'tickets',
-    Queues: 'queues'
+    Queues: 'queues',
+    Articles: 'articles'
 };
 
 window.RT.Autocomplete.bind = function(from) {
@@ -84,6 +85,9 @@ window.RT.Autocomplete.bind = function(from) {
             };
         }
 
+        var queue = input.attr("data-autocomplete-queue");
+        if (queue) queryargs.push("queue=" + queue);
+
         var checkRight = input.attr("data-autocomplete-checkright");
         if (checkRight) queryargs.push("right=" + checkRight);
 

commit 0f33bf654e040f9afdae005810bb68f92428de98
Author: craig Kaiser <craig at bestpractical.com>
Date:   Wed Feb 21 14:43:41 2018 -0500

    Test default article content on ticket create

diff --git a/t/web/ticket-create-utf8.t b/t/web/ticket-create-utf8.t
index ebb2d5eab..76a7360b7 100644
--- a/t/web/ticket-create-utf8.t
+++ b/t/web/ticket-create-utf8.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 43;
+use RT::Test tests => 49;
 
 my $ru_test = "\x{442}\x{435}\x{441}\x{442}";
 my $ru_support = "\x{43f}\x{43e}\x{434}\x{434}\x{435}\x{440}\x{436}\x{43a}\x{430}";
@@ -86,3 +86,23 @@ foreach my $test_str ( $ru_test, $l1_test ) {
     }
 }
 
+my $article = RT::Article->new($RT::SystemUser);
+my ( $id, $msg ) = $article->Create(
+    Class   => 'General',
+    Name    => 'My Article',
+    'CustomField-Content' => 'My Article Test Content',
+);
+ok( $id, $msg );
+(my $ret, $msg) = $article->Load(1);
+ok ($ret, $msg);
+
+my $queue = RT::Queue->new(RT->SystemUser);
+$queue->Load('General');
+ok( $queue, 'Loaded General Queue' );
+($ret, $msg) = $queue->SetDefaultValue( Name => 'Article', Value => $article->Id);
+ok( $ret, $msg );
+
+ok $m->login(root => 'password'), "logged in";
+$m->goto_create_ticket('General');
+$m->scraped_id_is('Content', '#1: My Article <br />-------------- <br />Content: <br />------- <br />My Article Test Content <br />');
+

commit 75edfaca3963b3045dc12f6080d56dc6ad7a8927
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Jun 22 13:26:20 2018 -0400

    Push default article loading toward create

diff --git a/share/html/Elements/MessageBox b/share/html/Elements/MessageBox
index 74b7037c4..769ed4a58 100644
--- a/share/html/Elements/MessageBox
+++ b/share/html/Elements/MessageBox
@@ -46,7 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <textarea autocomplete="off" class="messagebox <% $Type eq 'text/html' ? 'richtext' : '' %>" <% $width_attr %>="<% $Width %>" rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>" placeholder="<% $Placeholder %>">
-% $m->comp('/Articles/Elements/IncludeArticle', %ARGS) if $IncludeArticle;
+% $m->comp('/Articles/Elements/IncludeArticle', %ARGS, ArticleId => $article_id) if $IncludeArticle;
 % $m->callback( %ARGS, SignatureRef => \$signature, DefaultRef => \$Default, MessageRef => $message );
 % if (RT->Config->Get("SignatureAboveQuote", $session{'CurrentUser'})) {
 <% $Default || '' %><% $signature %><% $message %></textarea>
@@ -101,6 +101,11 @@ if ( $IncludeSignature and $signature =~ /\S/ ) {
     $signature = '';
 }
 
+my $article_id;
+if ( $IncludeDefaultArticle && defined $QueueObj && $QueueObj->Id ) {
+    $article_id = $QueueObj->DefaultValue('Article') if $QueueObj->DefaultValue('Article');
+}
+
 # wrap="something" seems to really break IE + richtext
 my $wrap_type = $Type eq 'text/html' ? '' : 'wrap="soft"';
 
@@ -125,4 +130,6 @@ $IncludeArticle            => 1;
 $Type                      => RT->Config->Get('MessageBoxRichText',  $session{'CurrentUser'}) ? 'text/html' : 'text/plain';
 $SuppressAttachmentWarning => 0
 $Placeholder               => ''
+$IncludeDefaultArticle            => 0  # Preload a default article based on queue settings
+$QueueObj                  => undef
 </%ARGS>
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 26dc216b0..23662a8de 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -240,9 +240,11 @@
 % }
 % $m->callback( %ARGS, QueueObj => $QueueObj, CallbackName => 'BeforeMessageBox' );
 % if (exists $ARGS{Content}) {
-<& /Elements/MessageBox, Default => $ARGS{Content}, IncludeSignature => 0 &>
+<& /Elements/MessageBox, Default => $ARGS{Content}, IncludeSignature => 0, IncludeDefaultArticle => 0 &>
+% } elsif ( $QuoteTransaction ) {
+<& /Elements/MessageBox, QuoteTransaction => $QuoteTransaction, IncludeDefaultArticle => 0 &>
 % } else {
-<& /Elements/MessageBox, QuoteTransaction => $QuoteTransaction &>
+<& /Elements/MessageBox, QueueObj => $QueueObj, IncludeDefaultArticle => 1 &>
 %}
 % $m->callback( %ARGS, QueueObj => $QueueObj, CallbackName => 'AfterMessageBox' );
 

commit bf48ed21460e46e0656e658149e8188d2af48590
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Sun Jun 24 12:16:44 2018 -0400

    Refactor ticket create and update to use new SelectArticle
    
    Remove multiple article selection options from the ticket update
    page and replace with a single article selection control. This
    new article selector is a dropdown like the former hotlist,
    but converts to an autocomplete when the number of items in the list
    exceeds the DropdownLimit configuration.
    
    This change removes some extra code that worked to load a ticket
    object in IncludeArticle. The template no longer needs it, but
    it was also passed to a callback. Extensions that use this
    callback may need to make changes to handle receiving an empty
    ticket object.

diff --git a/share/html/Articles/Elements/BeforeMessageBox b/share/html/Articles/Elements/BeforeMessageBox
index 972688d19..9e40ac496 100644
--- a/share/html/Articles/Elements/BeforeMessageBox
+++ b/share/html/Articles/Elements/BeforeMessageBox
@@ -52,45 +52,11 @@
 <table class="articles-select-article">
 % unless (RT->Config->Get('HideArticleSearchOnReplyCreate')) {
 <tr>
-<td><&|/l&>Search for Articles matching</&></td>
-<td><input size=20 name="<% $name_prefix %>Articles_Content" /></td>
-</tr>
-<tr>
 <td><&|/l&>Include Article:</&></td>
-<td><input size=20 name="<% $name_prefix %>Articles-Include-Article-Named" /></td>
+<td><& /Elements/SelectArticle, QueueObj => $QueueObj, AutoSubmit => 1 &></td>
 <td><input type="submit" name="Go" value="Go" /></td>
 </tr>
 % }
-% if ($hotlist->Count) {
-<tr>
-<td><&|/l&>Select an Article to include</&></td>
-<td><select name="<% $name_prefix %>Articles-Include-Article-Named-Hotlist" onchange="this.form.submit()">
-<option value="" selected><&|/l&>-</&></option>
-% while (my $article = $hotlist->Next) {
-<option value="<% $article->Id %>"><%$article->Name|| loc('(no name)')%>: <%$article->Summary || ''%></option>
-% }
-</select>
-</td>
-<td><input type="submit" name="Go" value="Go" /></td>
-</tr>
-% }
-% my %dedupe_articles;
-% while (my $article = $articles_content->Next) {
-%   $dedupe_articles{$article->Id}++;
-<tr>
-<td> </td>
-<td><%$article->Name|| loc('(no name)')%>: <%$article->Summary%></td>
-<td><input type="submit" name="<% $name_prefix %>Articles-Include-Article-<%$article->Id%>" value="Go" /></td>
-</tr>
-% }
-% while (my $article = $articles_basics->Next) {
-%   next if $dedupe_articles{$article->Id};
-<tr>
-<td> </td>
-<td><%$article->Name || loc('(no name)')%>: <%$article->Summary || ''%></td>
-<td><input type="submit" name="<% $name_prefix %>Articles-Include-Article-<%$article->Id%>" value="Go" /></td>
-</tr>
-% }
 % if ( @$topics ) {
 <tr>
 <td>
@@ -114,7 +80,7 @@
 <&|/l, $included_topic->Name &>Select an Article from [_1]</&>
 </td>
 <td>
-<select name="<% $name_prefix %>Articles-Include-Article" onchange="this.form.submit()">
+<select name="IncludeArticleId" onchange="this.form.submit()">
 <option value="" selected>-</option>
 % while ( my $art = $topic_articles->Next ) {
 <option value="<% $art->id %>"><%$art->Name||loc('(no name)')%>: <%$art->Summary%></option>
diff --git a/share/html/Articles/Elements/IncludeArticle b/share/html/Articles/Elements/IncludeArticle
index 0f4becbe5..848b00e3a 100644
--- a/share/html/Articles/Elements/IncludeArticle
+++ b/share/html/Articles/Elements/IncludeArticle
@@ -46,65 +46,21 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%INIT>
+# Nothing to do if we don't get an article id
+$IncludeArticleId //= $ARGS{'IncludeArticleId'};
+return unless $IncludeArticleId;
 
-my $parent_args = $m->caller_args(-1);
-my @articles;
+my $article = RT::Article->new($session{'CurrentUser'});
+my ($ret, $msg) = $article->Load($IncludeArticleId);
 
-my $name_prefix = '';
-$name_prefix = $ARGS{'Name'} .'-'
-    if $ARGS{'Name'}
-    && grep rindex($_, "$ARGS{'Name'}-Articles-", 0) == 0,
-        keys %$parent_args;
-
-my $Ticket = $ARGS{Ticket};
-if ( !$Ticket and $parent_args->{id} and $parent_args->{id} ne 'new' ) {
-    $Ticket = RT::Ticket->new($session{'CurrentUser'});
-    $Ticket->Load($parent_args->{id});
-    unless ( $Ticket->id ) {
-        $RT::Logger->error("Couldn't load ticket ". $parent_args->{id} )
-    }
-}
-
-foreach my $arg ( keys %$parent_args ) {
-    next if $name_prefix && substr($arg, 0, length($name_prefix)) ne $name_prefix;
-
-    my $Queue = RT::Queue->new($session{CurrentUser});
-    if ($Ticket && $Ticket->Id) {
-        $Queue = $Ticket->QueueObj;
-    }
-
-    my $article = RT::Article->new($session{'CurrentUser'});
-    $article->LoadByInclude(
-        Field => substr($arg, length($name_prefix)),
-        Value => $parent_args->{$arg},
-        Queue => $Queue->Id,
-    );
-    next unless $article && $article->id;
-    push (@articles, $article);
-}
-
-if ( !@articles && ( !$parent_args->{id} || $parent_args->{id} eq 'new' ) && $parent_args->{'Queue'} ) {
-    my $queue_id = $parent_args->{'Queue'};
-    my $QueueObj = RT::Queue->new($session{'CurrentUser'});
-    my ($ret, $msg) = $QueueObj->Load( $queue_id );
-    $RT::Logger->error($msg) unless ($ret);
-
-    if ( $QueueObj->id && $QueueObj->DefaultValue('Article') ) {
-        my $article = RT::Article->new($session{'CurrentUser'});
-        my ($ret, $msg) = $article->Load( $QueueObj->DefaultValue('Article') );
-        $RT::Logger->error($msg) unless ($ret);
-        if ($ret) {
-            push (@articles, $article);
-        }
-    }
-}
-
-foreach my $article (@articles) {
+if ( $ret && $article->Id ){
     my $formatted_article = $m->scomp('/Articles/Article/Elements/Preformatted',
-        Article => $article, Ticket => $Ticket
+        Article => $article
     );
 
-    $m->callback( Article => $article, Ticket => $Ticket, formatted_article => \$formatted_article );
+    # Ticket is provided in this callback for backward compatibility.
+    # However, after refactoring, less work is done to make sure a Ticket is available
+    $m->callback( Article => $article, Ticket => $ARGS{'Ticket'}, formatted_article => \$formatted_article );
 
     if (RT->Config->Get('MessageBoxRichText',  $session{'CurrentUser'})) {
         $formatted_article =~ s/>/>/g;
@@ -113,7 +69,11 @@ foreach my $article (@articles) {
         $formatted_article =~ s/\n/\n<br \/>/g;
     }
     $m->print($formatted_article);
-
 }
-return;
+else {
+    RT::Logger->error("Unable to load article $IncludeArticleId: $msg");
+}
 </%INIT>
+<%ARGS>
+$IncludeArticleId => undef
+</%ARGS>
diff --git a/share/html/Elements/MessageBox b/share/html/Elements/MessageBox
index 769ed4a58..8f8f34607 100644
--- a/share/html/Elements/MessageBox
+++ b/share/html/Elements/MessageBox
@@ -46,7 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <textarea autocomplete="off" class="messagebox <% $Type eq 'text/html' ? 'richtext' : '' %>" <% $width_attr %>="<% $Width %>" rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>" placeholder="<% $Placeholder %>">
-% $m->comp('/Articles/Elements/IncludeArticle', %ARGS, ArticleId => $article_id) if $IncludeArticle;
+% $m->comp('/Articles/Elements/IncludeArticle', %ARGS, IncludeArticleId => $article_id) if $IncludeArticle;
 % $m->callback( %ARGS, SignatureRef => \$signature, DefaultRef => \$Default, MessageRef => $message );
 % if (RT->Config->Get("SignatureAboveQuote", $session{'CurrentUser'})) {
 <% $Default || '' %><% $signature %><% $message %></textarea>
@@ -103,8 +103,13 @@ if ( $IncludeSignature and $signature =~ /\S/ ) {
 
 my $article_id;
 if ( $IncludeDefaultArticle && defined $QueueObj && $QueueObj->Id ) {
+    # Load a default article
     $article_id = $QueueObj->DefaultValue('Article') if $QueueObj->DefaultValue('Article');
 }
+else {
+    # Load from the page, if provided
+    $article_id = $ARGS{'IncludeArticleId'} if $ARGS{'IncludeArticleId'};
+}
 
 # wrap="something" seems to really break IE + richtext
 my $wrap_type = $Type eq 'text/html' ? '' : 'wrap="soft"';
@@ -130,6 +135,6 @@ $IncludeArticle            => 1;
 $Type                      => RT->Config->Get('MessageBoxRichText',  $session{'CurrentUser'}) ? 'text/html' : 'text/plain';
 $SuppressAttachmentWarning => 0
 $Placeholder               => ''
-$IncludeDefaultArticle            => 0  # Preload a default article based on queue settings
+$IncludeDefaultArticle     => 0  # Preload a default article based on queue settings
 $QueueObj                  => undef
 </%ARGS>
diff --git a/share/html/Elements/SelectArticle b/share/html/Elements/SelectArticle
index 754f2aabb..77dfdaca3 100644
--- a/share/html/Elements/SelectArticle
+++ b/share/html/Elements/SelectArticle
@@ -49,12 +49,13 @@
 % if ( $autocomplete ) {
 <& "SelectArticleAutocomplete", QueueObj => $QueueObj, Default => $DefaultArticle, Name => $ArticleSelectName &>
 % } else {
-<select name="<% $Name %>">
+<select name="<% $ArticleSelectName %>" <% $AutoSubmit ? 'onchange="this.form.submit()"' : '' |n%>>
 <option value="">-</option>
 % while (my $article = $articles->Next) {
 <option <% ( $article->Name eq $DefaultArticle) ? qq[ selected="selected"] : '' |n %>
-    value="<%$article->Id%>"
-><% $article->Name %></option>
+    value="<%$article->Id%>">
+<%$article->Name || loc('(no name)')%><% $IncludeSummary ? ': ' . $article->Summary || '' : '' %>
+</option>
 % }
 </select>
 % }
@@ -85,6 +86,8 @@ if ( $DefaultArticle and $DefaultArticle =~ /^\d+$/ ){
 
 <%ARGS>
 $QueueObj
-$ArticleSelectName => 'Article'
-$DefaultArticle => ''
+$ArticleSelectName => 'IncludeArticleId'
+$DefaultArticle    => ''
+$AutoSubmit        => 0
+$IncludeSummary    => 1
 </%ARGS>
diff --git a/share/html/Elements/SelectArticleAutocomplete b/share/html/Elements/SelectArticleAutocomplete
index 18ada32fa..16a8300c7 100644
--- a/share/html/Elements/SelectArticleAutocomplete
+++ b/share/html/Elements/SelectArticleAutocomplete
@@ -45,10 +45,12 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<input data-autocomplete="Articles" <% $QueueObj && $QueueObj->id ? q{data-autocomplete-queue="} . $QueueObj->id . q{"} : '' |n %> data-autocomplete-return="Name" name="<% $Name %>" value="<% $Default %>">
+<input data-autocomplete="Articles" <% $QueueObj && $QueueObj->id ? q{data-autocomplete-queue="} . $QueueObj->id . q{"} : '' |n %> data-autocomplete-return="id" name="<% $Name %>" value="<% $Default %>"
+<% $AutoSubmit ? 'data-autocomplete-autosubmit=1' : '' %>>
 
 <%ARGS>
 $QueueObj => undef
 $Default => undef
 $Name => 'Article'
+$AutoSubmit => 1
 </%ARGS>
diff --git a/t/articles/queue-specific-class.t b/t/articles/queue-specific-class.t
index 5cd042910..98a850791 100644
--- a/t/articles/queue-specific-class.t
+++ b/t/articles/queue-specific-class.t
@@ -2,9 +2,10 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 56;
+use RT::Test tests => undef;
 
 my ( $url, $m ) = RT::Test->started_ok;
+diag "Running server at: $url";
 $m->login;
 
 my %class = map { $_ => '' } qw/foo bar/;
@@ -73,36 +74,8 @@ diag "update ticket to see if there is article foo"
 {
     $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id,
         'ticket update page' );
-    $m->content_contains( 'article foo:', 'got article foo in hotlist' );
-    $m->content_lacks( 'article bar:', 'no article bar in hotlist' );
-
-    $m->submit_form(
-        form_number => 3,
-        fields      => { 'Articles_Content' => 'article' },
-        button      => 'Go',
-    );
-    $m->content_like( qr/article foo.*article foo/s, 'selected article foo' );
-    $m->content_lacks( 'article bar', 'no article bar' );
-
-    $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id,
-        'ticket update page' );
-    $m->submit_form(
-        form_number => 3,
-        fields      => { 'Articles-Include-Article-Named' => 'article foo' },
-        button      => 'Go',
-    );
-    $m->content_like( qr/article foo.*article foo/s, 'selected article foo' );
-    $m->content_lacks( 'article bar', 'no article bar' );
-
-    $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id,
-        'ticket update page' );
-    $m->submit_form(
-        form_number => 3,
-        fields      => { 'Articles-Include-Article-Named' => 'articlei bar' },
-        button      => 'Go',
-    );
-    $m->content_unlike( qr/article foo.*article foo/s, 'no article foo' );
-    $m->content_lacks( 'article bar', 'no article bar' );
+    $m->content_contains( 'article foo:', 'got article foo in dropdown' );
+    $m->content_lacks( 'article bar:', 'no article bar in dropdown' );
 }
 
 diag "apply bar to globally" if $ENV{TEST_VERBOSE};
@@ -123,36 +96,15 @@ diag "update ticket to see if there are both article foo and bar"
 {
     $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id,
         'ticket update page' );
-    $m->content_contains( 'article foo:', 'got article foo in hotlist' );
-    $m->content_contains( 'article bar:', 'got article bar in hotlist' );
-
-    $m->submit_form(
-        form_number => 3,
-        fields      => { 'Articles_Content' => 'article' },
-        button      => 'Go',
-    );
-    $m->content_like( qr/article foo.*article foo/s, 'selected article foo' );
-    $m->content_like( qr/article bar.*article bar/s, 'selected article bar' );
+    $m->content_contains( 'article foo:', 'got article foo in dropdown' );
+    $m->content_contains( 'article bar:', 'got article bar in dropdown' );
 
-    $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id,
-        'ticket update page' );
     $m->submit_form(
         form_number => 3,
-        fields      => { 'Articles-Include-Article-Named' => 'article foo' },
+        fields      => { 'IncludeArticleId' => '1' },
         button      => 'Go',
     );
     $m->content_like( qr/article foo.*article foo/s, 'selected article foo' );
-    $m->content_unlike( qr/article bar.*article bar/s, 'no article bar' );
-
-    $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id,
-        'ticket update page' );
-    $m->submit_form(
-        form_number => 3,
-        fields      => { 'Articles-Include-Article-Named' => 'article bar' },
-        button      => 'Go',
-    );
-    $m->content_like( qr/article bar.*article bar/s, 'selected article bar' );
-    $m->content_unlike( qr/article foo.*article foo/s, 'no article foo' );
 }
 
 
@@ -174,7 +126,7 @@ diag "remove both foo and bar" if $ENV{TEST_VERBOSE};
         fields      => { 'RemoveClass-' . $class{bar} => 0 },
         button      => 'UpdateObjs',
     );
-    $m->content_contains( 'Object deleted', 'remoked bar' );
+    $m->content_contains( 'Object deleted', 'removed bar' );
 }
 
 diag "update ticket to see if there are both article foo and bar"
@@ -215,3 +167,4 @@ diag "update ticket to see if there are both article foo and bar"
     $m->content_lacks( 'article bar', 'no article bar' );
 }
 
+done_testing();

commit d21aafd9e749cff4faf9c68d2c309cfdb46c75a2
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Sun Jun 24 13:13:10 2018 -0400

    Add articles notes to upgrading docs

diff --git a/devel/docs/UPGRADING-4.6 b/devel/docs/UPGRADING-4.6
new file mode 100644
index 000000000..747e45e3d
--- /dev/null
+++ b/devel/docs/UPGRADING-4.6
@@ -0,0 +1,17 @@
+=head1 UPGRADING FROM RT 4.4.0 and greater
+
+This documentation notes internals changes between the 4.4 and 4.6
+series that are primarily of interest to developers writing extensions
+or local customizations.  It is not an exhaustive list.
+
+=over
+
+=item *
+
+The default callback in C<Articles/Elements/IncludeArticle> provides a ticket
+object. However, the template itself does not need this ticket object, so it
+is no longer guaranteed to be loaded when it is passed.
+
+=back
+
+=cut
diff --git a/docs/UPGRADING-4.6 b/docs/UPGRADING-4.6
index 6755ffa17..54a97f9fc 100644
--- a/docs/UPGRADING-4.6
+++ b/docs/UPGRADING-4.6
@@ -18,6 +18,12 @@ The variables which alter the set of HTML elements allowed in HTML
 scrubbing have moved; they have been renamed, and are now found under
 L<RT::Interface::Web::Scrubber>.
 
+=item *
+
+The articles interface on tickets has been simplified, now showing only
+a dropdown for selecting articles. This dropdown converts to an autocomplete
+box when the dropdown contains more than C<$DropdownMenuLimit> items.
+
 =back
 
 =cut

commit bebc696c7a6e57523fc3a35ebc04e2d091667b4f
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Sun Jun 24 15:42:01 2018 -0400

    Remove article loading now handled in SelectArticle

diff --git a/share/html/Articles/Elements/BeforeMessageBox b/share/html/Articles/Elements/BeforeMessageBox
index 9e40ac496..5a61d6a0e 100644
--- a/share/html/Articles/Elements/BeforeMessageBox
+++ b/share/html/Articles/Elements/BeforeMessageBox
@@ -113,11 +113,6 @@ if ( $ARGS{'MessageBoxName'} ) {
     $name_prefix = $ARGS{'MessageBoxName'} .'-';
 }
 
-# convert Articles-Include-Article => $id to Articles-Include-Article-$id
-if ( my $tmp = $ARGS{$name_prefix ."Articles-Include-Article"} ) {
-    $ARGS{$name_prefix ."Articles-Include-Article-$tmp"}++;
-}
-
 my %uri;
 if ( $ARGS{id} && $ARGS{id} ne 'new' ) {
     $uri{$_}++ for split ' ', ($ARGS{$ARGS{'id'}.'-RefersTo'} || '');
@@ -136,34 +131,6 @@ if ( $ARGS{id} && $ARGS{id} ne 'new' ) {
     }
 }
 
-use RT::Articles;
-
-my $articles_content =
-  RT::Articles->new( $session{'CurrentUser'} );
-my $articles_basics = RT::Articles->new( $session{'CurrentUser'} );
-if ( my $tmp = $ARGS{ $name_prefix ."Articles_Content" } ) {
-    $articles_content->LimitCustomField(
-        VALUE => $tmp, OPERATOR => 'LIKE'
-    );
-    $articles_content->LimitAppliedClasses( Queue => $QueueObj );
-
-    $articles_basics->Limit( SUBCLAUSE       => 'all',
-                             FIELD           => 'Name',
-                             OPERATOR        => 'LIKE',
-                             VALUE           => $tmp,
-                             ENTRYAGGREGATOR => "OR" );
-    $articles_basics->Limit( SUBCLAUSE       => 'all',
-                             FIELD           => 'Summary',
-                             OPERATOR        => 'LIKE',
-                             VALUE           => $tmp,
-                             ENTRYAGGREGATOR => "OR" );
-    $articles_basics->LimitAppliedClasses( Queue => $QueueObj );
-}
-
-my $hotlist = RT::Articles->new( $session{'CurrentUser'} );
-$hotlist->LimitHotlistClasses;
-$hotlist->LimitAppliedClasses( Queue => $QueueObj );
-
 my ( $topic_articles, $topics, $included_topic );
 $topic_articles = RT::Articles->new( $session{CurrentUser} );
 $topics = [];

commit 07d60e912727ea169652b8d2cc13329c3b9c1a0f
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Sun Jun 24 15:45:37 2018 -0400

    Update article to ticket linking with new args

diff --git a/share/html/Articles/Elements/BeforeMessageBox b/share/html/Articles/Elements/BeforeMessageBox
index 5a61d6a0e..4bf3b77e2 100644
--- a/share/html/Articles/Elements/BeforeMessageBox
+++ b/share/html/Articles/Elements/BeforeMessageBox
@@ -117,17 +117,11 @@ my %uri;
 if ( $ARGS{id} && $ARGS{id} ne 'new' ) {
     $uri{$_}++ for split ' ', ($ARGS{$ARGS{'id'}.'-RefersTo'} || '');
 
-    foreach my $arg (keys %ARGS) {
-        next if $name_prefix && substr($arg, 0, length($name_prefix)) ne $name_prefix;
-
-        my $article = RT::Article->new($session{'CurrentUser'});
-        $article->LoadByInclude(
-            Field => substr($arg, length($name_prefix)),
-            Value => $ARGS{$arg},
-        );
-        if ($article->Id) {
-            $uri{$article->URI}++;
-        }
+    my $article = RT::Article->new($session{'CurrentUser'});
+    my ($ret, $msg) = $article->Load($ARGS{'IncludeArticleId'});
+
+    if ($ret && $article->Id) {
+        $uri{$article->URI}++;
     }
 }
 

commit 4f3ed5dce9505b5ab5fa0ba8dce2acd00517c1f5
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Sun Jun 24 15:31:31 2018 -0400

    Update tests for new articles configuration

diff --git a/t/articles/queue-specific-class.t b/t/articles/queue-specific-class.t
index 98a850791..cfd87d62a 100644
--- a/t/articles/queue-specific-class.t
+++ b/t/articles/queue-specific-class.t
@@ -36,7 +36,9 @@ for my $name ( keys %class ) {
 
     $m->submit_form(
         form_number => 2,
-        fields      => { Name => "article $name" }
+        fields      => { Name => "article $name",
+                         'Summary' => "$name summary",
+                         'Object-RT::Article--CustomField-1-Values' => "$name content"}
     );
 
     $m->content_like( qr/Article \d+ created/, "created article $name" );
@@ -104,7 +106,7 @@ diag "update ticket to see if there are both article foo and bar"
         fields      => { 'IncludeArticleId' => '1' },
         button      => 'Go',
     );
-    $m->content_like( qr/article foo.*article foo/s, 'selected article foo' );
+    $m->content_like( qr/foo summary/s, 'article included' );
 }
 
 
@@ -167,4 +169,5 @@ diag "update ticket to see if there are both article foo and bar"
     $m->content_lacks( 'article bar', 'no article bar' );
 }
 
+undef $m;
 done_testing();
diff --git a/t/web/articles-links.t b/t/web/articles-links.t
index 4aa8f9133..018f71c0b 100644
--- a/t/web/articles-links.t
+++ b/t/web/articles-links.t
@@ -29,7 +29,7 @@ $m->goto_ticket($ticket->id);
 $m->follow_link_ok({text => 'Reply'});
 
 $m->form_name('TicketUpdate');
-$m->field('Articles-Include-Article-Named' => $article->Name);
+$m->field('IncludeArticleId' => $article->Id);
 $m->submit;
 
 $m->content_contains('instance of ticket #17421', 'got the name of the article in the ticket');

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


More information about the rt-commit mailing list