[Bps-public-commit] rt-extension-configindatabase branch, master, updated. 9be38f8918bde78970031804ce11fc475be7ec49

Shawn Moore shawn at bestpractical.com
Tue Aug 15 14:15:20 EDT 2017


The branch, master has been updated
       via  9be38f8918bde78970031804ce11fc475be7ec49 (commit)
       via  e83711e8463addeba72a735c4b50cbcf7739af04 (commit)
       via  5497c1cdea5e36c393207d24a66658568343da03 (commit)
       via  82a56476d9475b1af9ef74cbe6c670bf7ebcc9d9 (commit)
       via  6f5895be1aafcc2c90a1429aee4391d53d6978ff (commit)
       via  f0eaaaf1b75b138445fe78cedb4cade78181dfdc (commit)
       via  fa58307632d0d71ea941a5370fd42a1ab1bd25fb (commit)
       via  25af4e4c9d400294ffdaefdb55f7dbc05caa3097 (commit)
       via  42e43cb6b96e1cff3c8fca7c6ff3a192077598e6 (commit)
       via  55517aec32338028f8c275c641c6626c3f48d288 (commit)
       via  90437d486a27f47c0459c8caa9a8de25f3218c9a (commit)
       via  a18af773bb37ecf65a2ce5a1ef177dbbae6279cd (commit)
       via  bdbf85e7121260f7d3bbdc33253794a4b2144e1b (commit)
       via  9d2b78cddebf1f6c7ea827870bf76ba81a5da0ab (commit)
       via  838528bf9d6c4c187cd5f1eecf5a8bccfa1218e3 (commit)
       via  f64166fdffa559b00f862a1fdf17ef58ed8a7aff (commit)
       via  ca93ea85b527663535d92212f15b6addaa60f0b8 (commit)
       via  998139ad88b29e3d2a733c679e848b74c93c8f36 (commit)
       via  c43afee73951b658895c5183ab129cb3093d8a90 (commit)
       via  63ddb4281564fc6faf7229518b49ef8c8040b35d (commit)
      from  7cf16a8eaf7d8832a2eef8af5e6794f0422fc775 (commit)

Summary of changes:
 html/Admin/Tools/EditConfig.html     | 177 ++++++++++++++++++++++-------------
 lib/RT/DatabaseSetting.pm            |  58 ++++++++----
 lib/RT/Extension/ConfigInDatabase.pm | 120 +++++++++++++++++++++---
 static/css/config-in-database.css    |   6 +-
 4 files changed, 267 insertions(+), 94 deletions(-)

- Log -----------------------------------------------------------------
commit 63ddb4281564fc6faf7229518b49ef8c8040b35d
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 15:43:02 2017 +0000

    Reorganize template for better flow

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 6cfe68c..1f1120d 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -134,24 +134,33 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
 
     my $doc_url = "https://docs.bestpractical.com/rt/$doc_version/RT_Config.html#$key";
 
+    my $widget = $meta->{'Widget'} || '/Widgets/Form/Code';
+    my $is_code = $widget eq '/Widgets/Form/Code';
+    my $is_password = ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ );
+    my $current_value = $is_code ? $val : $raw_value;
+    my $is_immutable = $meta->{Immutable}
+                    || $meta->{Obfuscate}
+                    || ($is_code && !$session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System));
+
+    my $args   = $meta->{'WidgetArguments'} || {};
 </%PERL>
 <tr class="<% $index_conf%2 ? 'oddline' : 'evenline'%>">
 <td class="collection-as-table"><a href="<% $doc_url %>" target="_blank"><% $key %></a></td>
 <td class="collection-as-table">
+
 % if ( $meta->{EditLink} ) {
 <&|/l_unsafe, "<a href=\"$meta->{EditLink}\">", loc($meta->{EditLinkLabel}), "</a>" &>Visit [_1][_2][_3] to manage this setting</&>
-% } elsif ( $meta->{Immutable} || $meta->{Obfuscate} || ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ )) {
-% unless ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ ) {
-<input type="text" disabled width="80" value="<% $val %>"></input><br>
 % }
+</ul>
+<br><em><% loc('Must modify in config file' ) %></em>
+% } elsif ( $is_password ) {
 <em><% loc('Must modify in config file' ) %></em>
+% } elsif ( $is_immutable ) {
+% if ($widget eq '/Widgets/Form/MultilineString' || $widget eq '/Widgets/Form/Code') {
+<textarea disabled class="<% $is_code ? 'code' : '' %>" rows="6" cols="80"><% $current_value %></textarea>
 % } else {
-% my $widget = $meta->{'Widget'} || '/Widgets/Form/Code';
-% my $args   = $meta->{'WidgetArguments'} || {};
-% my $current_value = $widget eq '/Widgets/Form/Code' ? $val : $raw_value;
-
-% if ($widget eq '/Widgets/Form/Code' && !$session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System)) {
-<textarea disabled rows="6" cols="80"><% $val %></textarea>
+<input type="text" disabled width="80" value="<% $current_value %>"></input>
+% }
 <br><em><% loc('Must modify in config file' ) %></em>
 % } else {
   <& $widget,
@@ -167,7 +176,6 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
   &>
 <textarea class="hidden" name="<% $key %>-Current"><% $current_value %></textarea>
 % }
-% }
 </td>
 </tr>
 % }
diff --git a/static/css/config-in-database.css b/static/css/config-in-database.css
index 623cac5..1af6435 100644
--- a/static/css/config-in-database.css
+++ b/static/css/config-in-database.css
@@ -9,7 +9,8 @@ body#comp-Admin-Tools-EditConfig input:disabled {
     background-color: #EEE;
 }
 
-body#comp-Admin-Tools-EditConfig .widget.code textarea {
+body#comp-Admin-Tools-EditConfig .widget.code textarea,
+body#comp-Admin-Tools-EditConfig textarea.code {
     font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
 }
 

commit c43afee73951b658895c5183ab129cb3093d8a90
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 15:43:19 2017 +0000

    Flag even immutable options as integer, string, etc

diff --git a/lib/RT/Extension/ConfigInDatabase.pm b/lib/RT/Extension/ConfigInDatabase.pm
index 1cdaa5c..afc9eea 100644
--- a/lib/RT/Extension/ConfigInDatabase.pm
+++ b/lib/RT/Extension/ConfigInDatabase.pm
@@ -57,7 +57,10 @@ for (qw/AllowUserAutocompleteForUnprivileged AlwaysDownloadAttachments
 
         HideOneTimeSuggestions LinkArticlesOnInclude
         MessageBoxUseSystemContextMenu
-        SelfServiceCorrespondenceOnly ShowSearchResultCount/) {
+        SelfServiceCorrespondenceOnly ShowSearchResultCount
+
+        DevelMode DisallowExecuteCode ExternalAuth
+    /) {
     next if !$RT::Config::META{$_};
 
     $RT::Config::META{$_}{Widget} = '/Widgets/Form/Boolean';
@@ -68,6 +71,7 @@ for (qw/AttachmentListCount AutoLogoff BcryptCost DefaultSummaryRows
         ExternalStorageCutoffSize LogoImageHeight LogoImageWidth LogoutRefresh
         MaxAttachmentSize MaxFulltextAttachmentSize MessageBoxRichTextHeight
         MinimumPasswordLength MoreAboutRequestorGroupsLimit TicketsItemMapSize
+        DatabasePort
        /) {
     next if !$RT::Config::META{$_};
 
@@ -82,7 +86,10 @@ for (qw/CommentAddress CorrespondAddress DashboardAddress DashboardSubject
         LogoLinkURL LogoURL MailCommand OwnerEmail
         RedistributeAutoGeneratedMessages SendmailArguments
         SendmailBounceArguments SendmailPath SetOutgoingMailFrom Timezone
-        WebImagesURL
+        WebImagesURL DatabaseAdmin DatabaseHost DatabaseName
+        DatabaseRTHost DatabaseType DatabaseUser Organization RecordBaseClass
+        WebBaseURL WebDomain WebPath WebPort WebSessionClass WebURL
+        rtname
        /) {
     next if !$RT::Config::META{$_};
 

commit 998139ad88b29e3d2a733c679e848b74c93c8f36
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 15:57:35 2017 +0000

    Add config name as a css class

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 1f1120d..2970156 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -144,7 +144,7 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
 
     my $args   = $meta->{'WidgetArguments'} || {};
 </%PERL>
-<tr class="<% $index_conf%2 ? 'oddline' : 'evenline'%>">
+<tr class="<% $key %> <% $index_conf%2 ? 'oddline' : 'evenline'%>">
 <td class="collection-as-table"><a href="<% $doc_url %>" target="_blank"><% $key %></a></td>
 <td class="collection-as-table">
 

commit ca93ea85b527663535d92212f15b6addaa60f0b8
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 15:57:46 2017 +0000

    Display /Plugins/ options as lists linking to metacpan

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 2970156..b1bf150 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -150,6 +150,10 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
 
 % if ( $meta->{EditLink} ) {
 <&|/l_unsafe, "<a href=\"$meta->{EditLink}\">", loc($meta->{EditLinkLabel}), "</a>" &>Visit [_1][_2][_3] to manage this setting</&>
+% } elsif ( $key =~ /Plugins/) {
+<ul class="plugins">
+% for my $plugin (RT->Config->Get($key)) {
+<li><a href="https://metacpan.org/search?q=<% $plugin |u %>" target="_blank"><% $plugin %></a></li>
 % }
 </ul>
 <br><em><% loc('Must modify in config file' ) %></em>
diff --git a/static/css/config-in-database.css b/static/css/config-in-database.css
index 1af6435..1415ddb 100644
--- a/static/css/config-in-database.css
+++ b/static/css/config-in-database.css
@@ -14,3 +14,6 @@ body#comp-Admin-Tools-EditConfig textarea.code {
     font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
 }
 
+body#comp-Admin-Tools-EditConfig ul.plugins {
+    margin: 0;
+}:

commit f64166fdffa559b00f862a1fdf17ef58ed8a7aff
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:19:26 2017 +0000

    Improve handling for booleans

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index b1bf150..ef56985 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -38,6 +38,11 @@ if (delete $ARGS{Update}) {
         my $prev = $ARGS{$key . '-Current'};
         next if $val eq $prev;
 
+        # for bools, check for truthiness since 0, '', and undef are equivalent
+        if ($widget eq '/Widgets/Form/Boolean') {
+            next if !!$val eq !!$prev;
+        }
+
         if ( $meta->{Immutable} || $meta->{Obfuscate} || ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ )) {
             push @results, loc("Cannot change [_1]: Permission Denied", $key);
             $has_error++;
@@ -143,6 +148,14 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
                     || ($is_code && !$session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System));
 
     my $args   = $meta->{'WidgetArguments'} || {};
+
+    if ($widget eq '/Widgets/Form/Boolean') {
+        %$args = (
+            Default => 0,
+            RadioStyle => 1,
+            %$args,
+        );
+    }
 </%PERL>
 <tr class="<% $key %> <% $index_conf%2 ? 'oddline' : 'evenline'%>">
 <td class="collection-as-table"><a href="<% $doc_url %>" target="_blank"><% $key %></a></td>

commit 838528bf9d6c4c187cd5f1eecf5a8bccfa1218e3
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:19:33 2017 +0000

    Improve input field for strings and integers

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index ef56985..1280fab 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -156,6 +156,13 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
             %$args,
         );
     }
+    elsif ($widget eq '/Widgets/Form/String' || $widget eq '/Widgets/Form/Integer') {
+        %$args = (
+            Size => 60,
+            %$args,
+        );
+    }
+
 </%PERL>
 <tr class="<% $key %> <% $index_conf%2 ? 'oddline' : 'evenline'%>">
 <td class="collection-as-table"><a href="<% $doc_url %>" target="_blank"><% $key %></a></td>

commit 9d2b78cddebf1f6c7ea827870bf76ba81a5da0ab
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:19:48 2017 +0000

    Avoid undef issues with config that isn't fully spec'd

diff --git a/lib/RT/Extension/ConfigInDatabase.pm b/lib/RT/Extension/ConfigInDatabase.pm
index afc9eea..3ac8588 100644
--- a/lib/RT/Extension/ConfigInDatabase.pm
+++ b/lib/RT/Extension/ConfigInDatabase.pm
@@ -146,20 +146,24 @@ sub LoadConfigFromDatabase {
 
         # are we inadvertantly overriding RT_SiteConfig.pm?
         my $meta = RT->Config->Meta($name);
-        my %source = %{ $meta->{'Source'} };
-        if ($source{'SiteConfig'} && $source{'File'} ne 'database') {
-            RT->Logger->warning("Change of config option '$name' at $source{File} line $source{Line} has been overridden by the config setting from the database. Please remove it from $source{File} or from the database to avoid confusion.");
+        if ($meta->{'Source'}) {
+            my %source = %{ $meta->{'Source'} };
+            if ($source{'SiteConfig'} && $source{'File'} ne 'database') {
+                RT->Logger->warning("Change of config option '$name' at $source{File} line $source{Line} has been overridden by the config setting from the database. Please remove it from $source{File} or from the database to avoid confusion.");
+            }
         }
 
+        my $type = $meta->{Type} || 'SCALAR';
+
         # hashes combine, but we don't want that behavior because the previous
         # config settings will shadow any change that the database config makes
-        if ($meta->{Type} eq 'HASH') {
+        if ($type eq 'HASH') {
             RT->Config->Set($name, ());
         }
 
-        my $val = $meta->{Type} eq 'ARRAY' ? $value
-                : $meta->{Type} eq 'HASH'  ? [ %$value ]
-                                           : [ $value ];
+        my $val = $type eq 'ARRAY' ? $value
+                : $type eq 'HASH'  ? [ %$value ]
+                                   : [ $value ];
 
         RT->Config->SetFromConfig(
             Option     => \$name,

commit bdbf85e7121260f7d3bbdc33253794a4b2144e1b
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:22:12 2017 +0000

    Only specify Default => 0 on 4.4.3+ which adds RadioStyle

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 1280fab..bf8803f 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -149,7 +149,9 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
 
     my $args   = $meta->{'WidgetArguments'} || {};
 
-    if ($widget eq '/Widgets/Form/Boolean') {
+    # RadioStyle was added to 4.4.3, and so if we are on an older version we
+    # can't specify Default => 0, since then we revert to just a checkbox
+    if ($widget eq '/Widgets/Form/Boolean' && RT::Handle::cmp_version($RT::VERSION,'4.4.3') >= 0 ) {
         %$args = (
             Default => 0,
             RadioStyle => 1,

commit a18af773bb37ecf65a2ce5a1ef177dbbae6279cd
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:27:33 2017 +0000

    Sort hash keys for consistency across processes

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index bf8803f..1cf7e82 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -17,6 +17,7 @@ sub stringify {
 
     local $Data::Dumper::Terse = 1;
     local $Data::Dumper::Indent = 2;
+    local $Data::Dumper::Sortkeys = 1;
     my $output = Dumper $value;
     chomp $output;
     return $output;

commit 90437d486a27f47c0459c8caa9a8de25f3218c9a
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:42:36 2017 +0000

    Implement syncing config changes across servers

diff --git a/lib/RT/Extension/ConfigInDatabase.pm b/lib/RT/Extension/ConfigInDatabase.pm
index 3ac8588..7e52faf 100644
--- a/lib/RT/Extension/ConfigInDatabase.pm
+++ b/lib/RT/Extension/ConfigInDatabase.pm
@@ -128,11 +128,16 @@ for (qw/DefaultSearchResultOrder/) {
 # special case due to being only for PostLoadCheck
 $RT::Config::META{RestrictReferrerLogin}{Invisible} = 1;
 
+my $config_cache_time;
+
 __PACKAGE__->LoadConfigFromDatabase();
 
 sub LoadConfigFromDatabase {
     my $class = shift;
 
+    $config_cache_time = time;
+    RT->Logger->info("Loading config from database");
+
     my $settings = RT::DatabaseSettings->new(RT->SystemUser);
     $settings->UnLimit;
 
@@ -176,6 +181,18 @@ sub LoadConfigFromDatabase {
     }
 }
 
+sub ConfigCacheNeedsUpdate {
+    my $self = shift;
+    my $time = shift;
+
+    if ($time) {
+        return RT->System->SetAttribute(Name => 'ConfigCacheNeedsUpdate', Content => $time);
+    } else {
+        my $cache = RT->System->FirstAttribute('ConfigCacheNeedsUpdate');
+        return (defined $cache ? $cache->Content : 0 );
+    }
+}
+
 sub ApplyConfigChangeToAllServerProcesses {
     my $class = shift;
 
@@ -183,9 +200,24 @@ sub ApplyConfigChangeToAllServerProcesses {
     $class->LoadConfigFromDatabase();
 
     # then notify other servers
-    # XXX
+    $class->ConfigCacheNeedsUpdate($config_cache_time);
 }
 
+do {
+    require RT::Interface::Web;
+    no warnings 'redefine';
+
+    my $orig_HandleRequest = RT::Interface::Web->can('HandleRequest');
+    *RT::Interface::Web::HandleRequest = sub {
+        my $needs_update = __PACKAGE__->ConfigCacheNeedsUpdate;
+        if ($needs_update > $config_cache_time) {
+            __PACKAGE__->LoadConfigFromDatabase();
+            $config_cache_time = $needs_update;
+        }
+        $orig_HandleRequest->(@_);
+    };
+};
+
 =head1 NAME
 
 RT-Extension-ConfigInDatabase - update RT config via admin UI

commit 55517aec32338028f8c275c641c6626c3f48d288
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:44:07 2017 +0000

    Apply all config changes once in a batch
    
    If you change a dozen config settings at once, no need to reload the
    config a dozen times. But one-off changes that don't opt into
    BeginConfigChanges/EndConfigChanges should still immediately propagate
    changes

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 1cf7e82..0b41e84 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -24,6 +24,7 @@ sub stringify {
 }
 
 if (delete $ARGS{Update}) {
+    RT::Extension::ConfigInDatabase->BeginConfigChanges;
     $RT::Handle->BeginTransaction;
     my $has_error;
 
@@ -110,6 +111,7 @@ if (delete $ARGS{Update}) {
     else {
         $RT::Handle->Commit;
     }
+    RT::Extension::ConfigInDatabase->EndConfigChanges;
 }
 
 </%INIT>
diff --git a/lib/RT/Extension/ConfigInDatabase.pm b/lib/RT/Extension/ConfigInDatabase.pm
index 7e52faf..568ee1e 100644
--- a/lib/RT/Extension/ConfigInDatabase.pm
+++ b/lib/RT/Extension/ConfigInDatabase.pm
@@ -193,9 +193,23 @@ sub ConfigCacheNeedsUpdate {
     }
 }
 
+my $in_config_change_txn = 0;
+sub BeginConfigChanges {
+    $in_config_change_txn = $in_config_change_txn + 1;
+}
+
+sub EndConfigChanges {
+    $in_config_change_txn = $in_config_change_txn - 1;
+    if (!$in_config_change_txn) {
+        shift->ApplyConfigChangeToAllServerProcesses();
+    }
+}
+
 sub ApplyConfigChangeToAllServerProcesses {
     my $class = shift;
 
+    return if $in_config_change_txn;
+
     # first apply locally
     $class->LoadConfigFromDatabase();
 

commit 42e43cb6b96e1cff3c8fca7c6ff3a192077598e6
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 16:48:18 2017 +0000

    Make sure we don't accidentally end up in a BeginConfigChanges txn

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 0b41e84..5e63c0d 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -28,80 +28,87 @@ if (delete $ARGS{Update}) {
     $RT::Handle->BeginTransaction;
     my $has_error;
 
-    for my $key (keys %ARGS) {
-        next if $key =~ /-Current$/;
-
-        my $meta = RT->Config->Meta( $key );
-        my $widget = $meta->{Widget} || '/Widgets/Form/Code';
-        my $is_code = $widget eq '/Widgets/Form/Code';
-
-        my $val = $ARGS{$key};
-        $val = '' if $val eq '__empty_value__';
-        my $prev = $ARGS{$key . '-Current'};
-        next if $val eq $prev;
-
-        # for bools, check for truthiness since 0, '', and undef are equivalent
-        if ($widget eq '/Widgets/Form/Boolean') {
-            next if !!$val eq !!$prev;
-        }
-
-        if ( $meta->{Immutable} || $meta->{Obfuscate} || ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ )) {
-            push @results, loc("Cannot change [_1]: Permission Denied", $key);
-            $has_error++;
-            next;
-        }
+    eval {
+        for my $key (keys %ARGS) {
+            next if $key =~ /-Current$/;
+
+            my $meta = RT->Config->Meta( $key );
+            my $widget = $meta->{Widget} || '/Widgets/Form/Code';
+            my $is_code = $widget eq '/Widgets/Form/Code';
+
+            my $val = $ARGS{$key};
+            $val = '' if $val eq '__empty_value__';
+            my $prev = $ARGS{$key . '-Current'};
+            next if $val eq $prev;
+
+            # for bools, check for truthiness since 0, '', and undef are equivalent
+            if ($widget eq '/Widgets/Form/Boolean') {
+                next if !!$val eq !!$prev;
+            }
 
-        if ($is_code) {
-            if (!$session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System)) {
+            if ( $meta->{Immutable} || $meta->{Obfuscate} || ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ )) {
                 push @results, loc("Cannot change [_1]: Permission Denied", $key);
                 $has_error++;
                 next;
             }
 
-            my $code = $val;
-            my $coderef;
-            # similar to RT::Scrip::CompileCheck
-            do {
-                no strict 'vars';
-                $coderef = eval "sub { $code \n }";
-            };
-            if ($@) {
-                my $error = $@;
-                push @results, loc("Couldn't compile [_1] codeblock '[_2]': [_3]", $key, $code, $error);
-                $has_error++;
-                next;
-            }
+            if ($is_code) {
+                if (!$session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System)) {
+                    push @results, loc("Cannot change [_1]: Permission Denied", $key);
+                    $has_error++;
+                    next;
+                }
 
-            if ($coderef) {
-                $val = eval { $coderef->() };
+                my $code = $val;
+                my $coderef;
+                # similar to RT::Scrip::CompileCheck
+                do {
+                    no strict 'vars';
+                    $coderef = eval "sub { $code \n }";
+                };
                 if ($@) {
                     my $error = $@;
-                    push @results, loc("Couldn't execute [_1] codeblock '[_2]': [_3]", $key, $code, $error);
+                    push @results, loc("Couldn't compile [_1] codeblock '[_2]': [_3]", $key, $code, $error);
                     $has_error++;
                     next;
                 }
-            }
-        }
 
-        my $setting = RT::DatabaseSetting->new($session{CurrentUser});
-        $setting->Load($key);
-        if ($setting->Id) {
-            if ($setting->Disabled) {
-                $setting->SetDisabled(0);
+                if ($coderef) {
+                    $val = eval { $coderef->() };
+                    if ($@) {
+                        my $error = $@;
+                        push @results, loc("Couldn't execute [_1] codeblock '[_2]': [_3]", $key, $code, $error);
+                        $has_error++;
+                        next;
+                    }
+                }
             }
 
-            my ($ok, $msg) = $setting->SetContent($val);
-            push @results, $msg;
-            $has_error++ if !$ok;
-        }
-        else {
-            my ($ok, $msg) = $setting->Create(
-                Name    => $key,
-                Content => $val,
-            );
-            push @results, $msg;
-            $has_error++ if !$ok;
+            my $setting = RT::DatabaseSetting->new($session{CurrentUser});
+            $setting->Load($key);
+            if ($setting->Id) {
+                if ($setting->Disabled) {
+                    $setting->SetDisabled(0);
+                }
+
+                my ($ok, $msg) = $setting->SetContent($val);
+                push @results, $msg;
+                $has_error++ if !$ok;
+            }
+            else {
+                my ($ok, $msg) = $setting->Create(
+                    Name    => $key,
+                    Content => $val,
+                );
+                push @results, $msg;
+                $has_error++ if !$ok;
+            }
         }
+    };
+
+    if ($@) {
+        push @results, $@;
+        $has_error++;
     }
 
     if ($has_error) {
diff --git a/lib/RT/Extension/ConfigInDatabase.pm b/lib/RT/Extension/ConfigInDatabase.pm
index 568ee1e..efc5394 100644
--- a/lib/RT/Extension/ConfigInDatabase.pm
+++ b/lib/RT/Extension/ConfigInDatabase.pm
@@ -223,6 +223,11 @@ do {
 
     my $orig_HandleRequest = RT::Interface::Web->can('HandleRequest');
     *RT::Interface::Web::HandleRequest = sub {
+        if ($in_config_change_txn) {
+            RT->Logger->error("It appears that there were unbalanced calls to BeginConfigChanges with EndConfigChanges; this indicates a software fault");
+            $in_config_change_txn = 0;
+        }
+
         my $needs_update = __PACKAGE__->ConfigCacheNeedsUpdate;
         if ($needs_update > $config_cache_time) {
             __PACKAGE__->LoadConfigFromDatabase();

commit 25af4e4c9d400294ffdaefdb55f7dbc05caa3097
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 17:03:02 2017 +0000

    When a database setting is disabled, restore what was in config files
    
    That includes the metadata about where the config option was set (Meta)

diff --git a/lib/RT/Extension/ConfigInDatabase.pm b/lib/RT/Extension/ConfigInDatabase.pm
index efc5394..817fead 100644
--- a/lib/RT/Extension/ConfigInDatabase.pm
+++ b/lib/RT/Extension/ConfigInDatabase.pm
@@ -3,6 +3,7 @@ use strict;
 use warnings;
 use RT::DatabaseSetting;
 use RT::DatabaseSettings;
+use Storable;
 
 our $VERSION = '0.01';
 
@@ -129,6 +130,7 @@ for (qw/DefaultSearchResultOrder/) {
 $RT::Config::META{RestrictReferrerLogin}{Invisible} = 1;
 
 my $config_cache_time;
+my %original_setting_from_files;
 
 __PACKAGE__->LoadConfigFromDatabase();
 
@@ -141,6 +143,8 @@ sub LoadConfigFromDatabase {
     my $settings = RT::DatabaseSettings->new(RT->SystemUser);
     $settings->UnLimit;
 
+    my %seen;
+
     while (my $setting = $settings->Next) {
         my $name = $setting->Name;
         my $value = $setting->Content;
@@ -149,6 +153,15 @@ sub LoadConfigFromDatabase {
         local $Data::Dumper::Terse = 1;
         RT->Logger->debug("from database: Set('$name', ".Dumper($value).");");
 
+        if (!exists $original_setting_from_files{$name}) {
+            $original_setting_from_files{$name} = [
+                scalar(RT->Config->Get($name)),
+                Storable::dclone(scalar(RT->Config->Meta($name))),
+            ];
+        }
+
+        $seen{$name}++;
+
         # are we inadvertantly overriding RT_SiteConfig.pm?
         my $meta = RT->Config->Meta($name);
         if ($meta->{'Source'}) {
@@ -179,6 +192,28 @@ sub LoadConfigFromDatabase {
             SiteConfig => 1,
         );
     }
+
+    # anything that wasn't loaded from the database but has been set in
+    # %original_setting_from_files must have been disabled from the database,
+    # so we want to restore the original setting
+    for my $name (keys %original_setting_from_files) {
+        next if $seen{$name};
+
+        my ($value, $meta) = @{ $original_setting_from_files{$name} };
+        my $type = $meta->{Type} || 'SCALAR';
+
+        if ($type eq 'ARRAY') {
+            RT->Config->Set($name, @$value);
+        }
+        elsif ($type eq 'HASH') {
+            RT->Config->Set($name, %$value);
+        }
+        else {
+            RT->Config->Set($name, $value);
+        }
+
+        %{ RT->Config->Meta($name) } = %$meta;
+    }
 }
 
 sub ConfigCacheNeedsUpdate {

commit fa58307632d0d71ea941a5370fd42a1ab1bd25fb
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 17:30:00 2017 +0000

    Don't get current user's preference, get config value

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 5e63c0d..b35c915 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -142,7 +142,7 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
 
     next if $meta->{Invisible};
 
-    my $raw_value = RT->Config->Get( $key, $session{'CurrentUser'} );
+    my $raw_value = RT->Config->Get( $key );
     my $val = stringify($raw_value);
 
     $index_conf++;

commit f0eaaaf1b75b138445fe78cedb4cade78181dfdc
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 17:31:10 2017 +0000

    Factor out $has_execute_code

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index b35c915..2f6fd38 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -4,6 +4,8 @@ unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'Super
  Abort(loc('This feature is only available to system administrators'));
 }
 
+my $has_execute_code = $session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System);
+
 my @results;
 
 my $doc_version = $RT::VERSION;
@@ -53,7 +55,7 @@ if (delete $ARGS{Update}) {
             }
 
             if ($is_code) {
-                if (!$session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System)) {
+                if (!$has_execute_code) {
                     push @results, loc("Cannot change [_1]: Permission Denied", $key);
                     $has_error++;
                     next;
@@ -155,7 +157,7 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
     my $current_value = $is_code ? $val : $raw_value;
     my $is_immutable = $meta->{Immutable}
                     || $meta->{Obfuscate}
-                    || ($is_code && !$session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System));
+                    || ($is_code && !$has_execute_code);
 
     my $args   = $meta->{'WidgetArguments'} || {};
 

commit 6f5895be1aafcc2c90a1429aee4391d53d6978ff
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 17:35:45 2017 +0000

    Improve logging and permission checks for RT::DatabaseSetting

diff --git a/lib/RT/DatabaseSetting.pm b/lib/RT/DatabaseSetting.pm
index e603f56..de306dc 100644
--- a/lib/RT/DatabaseSetting.pm
+++ b/lib/RT/DatabaseSetting.pm
@@ -97,9 +97,11 @@ sub Create {
     }
 
     if (!ref($content) && !ref($old_value)) {
+        RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name . " from " . $old_value . " to " . $content);
         return ($id, $self->loc("[_1] changed from [_2] to [_3]", $self->Name, $old_value, $content));
     }
     else {
+        RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name);
         return ($id, $self->loc("[_1] changed", $self->Name));
     }
 }
@@ -183,6 +185,7 @@ sub Delete {
     my ($ok, $msg) = $self->SUPER::Delete(@_);
     return ($ok, $msg) if !$ok;
     RT::Extension::ConfigInDatabase->ApplyConfigChangeToAllServerProcesses;
+    RT->Logger->info($self->CurrentUser->Name . " removed database setting for " . $self->Name);
     return ($ok, $self->loc("Database setting removed."));
 }
 
@@ -219,6 +222,8 @@ sub SetContent {
     my $value        = shift;
     my $content_type = shift || '';
 
+    return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
+
     my $old_value = $self->Content;
     unless (defined($old_value) && length($old_value)) {
         $old_value = $self->loc('(no value)');
@@ -256,8 +261,10 @@ sub SetContent {
     }
 
     if (!ref($value) && !ref($old_value)) {
+        RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name . " from " . $old_value . " to " . $value);
         return ($ok, $self->loc("[_1] changed from [_2] to [_3]", $self->Name, $old_value, $value));
     } else {
+        RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name);
         return ($ok, $self->loc("[_1] changed", $self->Name));
     }
 }

commit 82a56476d9475b1af9ef74cbe6c670bf7ebcc9d9
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 17:40:34 2017 +0000

    Use a lexical subroutine for stringify
    
    Otherwise the core /Admin/Tools/Configuration.html page, which also has
    a package-level stringify subroutine, competes with the one in this
    template, as both use package HTML::Mason::Commands. Since the two
    stringify()s do different things, you would get different results
    depending on which order you load the two pages.

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 2f6fd38..06e6e8b 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -13,7 +13,7 @@ $doc_version =~ s/rc\d+//; # 4.4.2rc1 -> 4.4.2
 $doc_version =~ s/\.\d+-\d+-g\w+$//;  # 4.4.3-1-g123 -> 4.4
 
 use Data::Dumper;
-sub stringify {
+my $stringify = sub {
     my $value = shift;
     return "" if !defined($value);
 
@@ -23,7 +23,7 @@ sub stringify {
     my $output = Dumper $value;
     chomp $output;
     return $output;
-}
+};
 
 if (delete $ARGS{Update}) {
     RT::Extension::ConfigInDatabase->BeginConfigChanges;
@@ -145,7 +145,7 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
     next if $meta->{Invisible};
 
     my $raw_value = RT->Config->Get( $key );
-    my $val = stringify($raw_value);
+    my $val = $stringify->($raw_value);
 
     $index_conf++;
 

commit 5497c1cdea5e36c393207d24a66658568343da03
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 17:47:57 2017 +0000

    Avoid missing config name on error
    
    When creating a new config setting, $self->Name isn't set yet, so
    if serialization fails there was no way of knowing which setting caused
    it

diff --git a/lib/RT/DatabaseSetting.pm b/lib/RT/DatabaseSetting.pm
index de306dc..b3ea007 100644
--- a/lib/RT/DatabaseSetting.pm
+++ b/lib/RT/DatabaseSetting.pm
@@ -69,7 +69,7 @@ sub Create {
     }
 
     if (ref ($args{'Content'}) ) {
-        $args{'Content'} = $self->_SerializeContent($args{'Content'});
+        $args{'Content'} = $self->_SerializeContent($args{'Content'}, $args{'Name'});
         if (!$args{'Content'}) {
          return (0, $@);
         }
@@ -312,9 +312,10 @@ sub _Value {
 sub _SerializeContent {
     my $self = shift;
     my $content = shift;
+    my $name = shift || $self->Name;
     my $frozen = eval { encode_base64(Storable::nfreeze($content)) };
     if ($@) {
-        $RT::Logger->error("Serialization of database setting ". $self->Name . " failed: $@");
+        $RT::Logger->error("Serialization of database setting $name failed: $@");
     }
 
     return $frozen;

commit e83711e8463addeba72a735c4b50cbcf7739af04
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 17:56:52 2017 +0000

    Capture and propagate errors more consistently

diff --git a/lib/RT/DatabaseSetting.pm b/lib/RT/DatabaseSetting.pm
index b3ea007..59bb1e0 100644
--- a/lib/RT/DatabaseSetting.pm
+++ b/lib/RT/DatabaseSetting.pm
@@ -69,9 +69,9 @@ sub Create {
     }
 
     if (ref ($args{'Content'}) ) {
-        $args{'Content'} = $self->_SerializeContent($args{'Content'}, $args{'Name'});
-        if (!$args{'Content'}) {
-         return (0, $@);
+        ($args{'Content'}, my $error) = $self->_SerializeContent($args{'Content'}, $args{'Name'});
+        if ($error) {
+            return (0, $error);
         }
         $args{'ContentType'} = 'storable';
     }
@@ -91,7 +91,7 @@ sub Create {
 
     RT::Extension::ConfigInDatabase->ApplyConfigChangeToAllServerProcesses;
 
-    my $content = $self->Content;
+    my ($content, $error) = $self->Content;
     unless (defined($content) && length($content)) {
         $content = $self->loc('(no value)');
     }
@@ -189,13 +189,13 @@ sub Delete {
     return ($ok, $self->loc("Database setting removed."));
 }
 
-=head2 Content
+=head2 DecodedContent
 
-Returns this setting's content.
+Returns a pair of this setting's content and any error.
 
 =cut
 
-sub Content {
+sub DecodedContent {
     my $self = shift;
 
     # Here we call _Value to run the ACL check.
@@ -207,10 +207,10 @@ sub Content {
         return $self->_DeserializeContent($content);
     }
     elsif ($type eq 'application/json') {
-        return JSON::from_json($content);
+        return $self->_DeJSONContent($content);
     }
 
-    return $content;
+    return ($content, "");
 }
 
 =head2 SetContent
@@ -224,15 +224,15 @@ sub SetContent {
 
     return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
 
-    my $old_value = $self->Content;
+    my ($old_value, $error) = $self->Content;
     unless (defined($old_value) && length($old_value)) {
         $old_value = $self->loc('(no value)');
     }
 
     if (ref $value) {
-        $value = $self->_SerializeContent($value);
-        if (!$value) {
-            return (0, $@);
+        ($value, my $error) = $self->_SerializeContent($value);
+        if ($error) {
+            return (0, $error);
         }
         $content_type = 'storable';
     }
@@ -314,8 +314,10 @@ sub _SerializeContent {
     my $content = shift;
     my $name = shift || $self->Name;
     my $frozen = eval { encode_base64(Storable::nfreeze($content)) };
-    if ($@) {
-        $RT::Logger->error("Serialization of database setting $name failed: $@");
+
+    if (my $error = $@) {
+        $RT::Logger->error("Storable serialization of database setting $name failed: $error");
+        return (undef, $self->loc("Storable serialization of database setting [_1] failed: [_2]", $name, $error));
     }
 
     return $frozen;
@@ -326,8 +328,22 @@ sub _DeserializeContent {
     my $content = shift;
 
     my $thawed = eval { Storable::thaw(decode_base64($content)) };
-    if ($@) {
-        $RT::Logger->error("Deserialization of database setting " . $self->Name . " failed: $@");
+    if (my $error = $@) {
+        $RT::Logger->error("Storable deserialization of database setting " . $self->Name . " failed: $error");
+        return (undef, $self->loc("Storable deserialization of database setting [_1] failed: [_2]", $self->Name, $error));
+    }
+
+    return $thawed;
+}
+
+sub _DeJSONContent {
+    my $self = shift;
+    my $content = shift;
+
+    my $thawed = eval { JSON::from_json($content) };
+    if (my $error = $@) {
+        $RT::Logger->error("JSON deserialization of database setting " . $self->Name . " failed: $error");
+        return (undef, $self->loc("JSON deserialization of database setting [_1] failed: [_2]", $self->Name, $error));
     }
 
     return $thawed;
diff --git a/lib/RT/Extension/ConfigInDatabase.pm b/lib/RT/Extension/ConfigInDatabase.pm
index 817fead..e7da405 100644
--- a/lib/RT/Extension/ConfigInDatabase.pm
+++ b/lib/RT/Extension/ConfigInDatabase.pm
@@ -147,7 +147,8 @@ sub LoadConfigFromDatabase {
 
     while (my $setting = $settings->Next) {
         my $name = $setting->Name;
-        my $value = $setting->Content;
+        my ($value, $error) = $setting->DecodedContent;
+        next if $error;
 
         use Data::Dumper;
         local $Data::Dumper::Terse = 1;

commit 9be38f8918bde78970031804ce11fc475be7ec49
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Aug 15 18:13:33 2017 +0000

    Any coderefs means the config setting is effectively immutable
    
    Must edit it in the config file because B::Deparse etc is not 100% accurate

diff --git a/html/Admin/Tools/EditConfig.html b/html/Admin/Tools/EditConfig.html
index 06e6e8b..8e4d171 100644
--- a/html/Admin/Tools/EditConfig.html
+++ b/html/Admin/Tools/EditConfig.html
@@ -154,11 +154,12 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
     my $widget = $meta->{'Widget'} || '/Widgets/Form/Code';
     my $is_code = $widget eq '/Widgets/Form/Code';
     my $is_password = ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ );
-    my $current_value = $is_code ? $val : $raw_value;
     my $is_immutable = $meta->{Immutable}
                     || $meta->{Obfuscate}
+                    || ($is_code && $val =~ s/sub { "DUMMY" }/sub { ... }/g)
                     || ($is_code && !$has_execute_code);
 
+    my $current_value = $is_code ? $val : $raw_value;
     my $args   = $meta->{'WidgetArguments'} || {};
 
     # RadioStyle was added to 4.4.3, and so if we are on an older version we

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


More information about the Bps-public-commit mailing list