[Rt-commit] rt branch, 4.6/configindatabase-themed, created. rt-4.4.4-552-g165ca1445f
? sunnavy
sunnavy at bestpractical.com
Tue Nov 19 17:57:20 EST 2019
The branch, 4.6/configindatabase-themed has been created
at 165ca1445fd4beb962b334471ac8dc34c91cde4b (commit)
- Log -----------------------------------------------------------------
commit b28dc304678142be889bf8057cb409241275dd90
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 16:01:59 2017 +0000
Allow specifying size for Integer and String widgets
diff --git a/share/html/Widgets/Form/Integer b/share/html/Widgets/Form/Integer
index fa48d6071f..918fe88b0e 100644
--- a/share/html/Widgets/Form/Integer
+++ b/share/html/Widgets/Form/Integer
@@ -78,10 +78,11 @@ $DefaultLabel => undef
</%ARGS>
<%METHOD InputOnly>
-<input type="text" name="<% $Name %>" value="<% $CurrentValue %>" class="form-control" />\
+<input type="text" name="<% $Name %>" size="<% $Size %>" value="<% $CurrentValue %>" class="form-control" />\
<%ARGS>
$Name
$CurrentValue => '',
+$Size => 20
</%ARGS>
<%INIT>
$CurrentValue = '' unless defined $CurrentValue;
diff --git a/share/html/Widgets/Form/String b/share/html/Widgets/Form/String
index 89da49761f..165daf2b9d 100644
--- a/share/html/Widgets/Form/String
+++ b/share/html/Widgets/Form/String
@@ -74,11 +74,12 @@ $DefaultLabel => loc( 'Default: [_1]', $DefaultValue ),
</%ARGS>
<%METHOD InputOnly>
-<input type="<% $Type %>" name="<% $Name %>" value="<% $CurrentValue || '' %>" class="form-control" />\
+<input type="<% $Type %>" name="<% $Name %>" size="<% $Size %>" value="<% $CurrentValue || '' %>" class="form-control" />\
<%ARGS>
$Name
$CurrentValue => '',
$Type => 'text'
+$Size => 20
</%ARGS>
</%METHOD>
commit 94744da2c654ff740b4722e5d6e741fcb47ec95b
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 16:02:18 2017 +0000
Allow String widget to have a value of "0"
Using $CurrentValue || '' means that 0 gets canonicalized to the empty
string, which means you can't have a default of 0.
This is necessary for RT::Extension::ConfigInDatabase's input field for
$SetOutgoingMailFrom, which is a string that takes an email address or
the special case value of "0".
diff --git a/share/html/Widgets/Form/String b/share/html/Widgets/Form/String
index 165daf2b9d..24cada9716 100644
--- a/share/html/Widgets/Form/String
+++ b/share/html/Widgets/Form/String
@@ -74,7 +74,7 @@ $DefaultLabel => loc( 'Default: [_1]', $DefaultValue ),
</%ARGS>
<%METHOD InputOnly>
-<input type="<% $Type %>" name="<% $Name %>" size="<% $Size %>" value="<% $CurrentValue || '' %>" class="form-control" />\
+<input type="<% $Type %>" name="<% $Name %>" size="<% $Size %>" value="<% $CurrentValue // '' %>" class="form-control" />\
<%ARGS>
$Name
$CurrentValue => '',
commit 59334314c9ea3c0d82016214999adebf6ae449a8
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 16:07:59 2017 +0000
Add RadioStyle option to Boolean widget
If you use a Boolean widget with no Default, then the Yes/No/(Default)
radio widget gets automatically downgraded to a checkbox. Instead, allow
RadioStyle => 1 to present a Yes/No radio widget
diff --git a/share/html/Widgets/Form/Boolean b/share/html/Widgets/Form/Boolean
index e509f94955..786553d663 100644
--- a/share/html/Widgets/Form/Boolean
+++ b/share/html/Widgets/Form/Boolean
@@ -70,10 +70,11 @@ $Name => undef,
$Default => 0,
$DefaultValue => 0,
$DefaultLabel => loc( 'Use default ([_1])', $DefaultValue? loc('Yes'): loc('No') ),
+$RadioStyle => 0
$CurrentValue => undef,
</%ARGS>
-% unless ( $Default ) {
+% if ( !$Default && !$RadioStyle ) {
<input type="hidden" name="<% $Name %>" value="0" />\
<div class="custom-control custom-checkbox">
<input type="checkbox" id="<% $Name %>" name="<% $Name %>" class="custom-control-input" value="1" <% $CurrentValue? ' checked="checked"': '' |n %>>
@@ -94,13 +95,14 @@ $CurrentValue => undef,
<label class="custom-control-label" for="<% $Name %>-no"><&|/l&>No</&></label>
</div>
</div>
-
+% if ($Default) {
<div class="col-md-auto">
<div class="custom-control custom-radio">
<input type="radio" id="<% $Name %>-empty" name="<% $Name %>" class="custom-control-input" value="__empty_value__" <% !defined $CurrentValue? ' checked="checked"': '' |n %>>
<label class="custom-control-label" for="<% $Name %>-empty"><% $DefaultLabel %></label>
</div>
</div>
+% }
</div>
% }
</%METHOD>
commit 534bba3102023c651d9bfc45e31f78e6c0655428
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 18:49:11 2017 +0000
Make booleans with RadioStyle use true/false logic
Rather than tri-value
diff --git a/share/html/Widgets/Form/Boolean b/share/html/Widgets/Form/Boolean
index 786553d663..3b7091b753 100644
--- a/share/html/Widgets/Form/Boolean
+++ b/share/html/Widgets/Form/Boolean
@@ -88,20 +88,26 @@ $CurrentValue => undef,
<label class="custom-control-label" for="<% $Name %>-yes"><&|/l&>Yes</&></label>
</div>
</div>
-
+% if ($Default) {
<div class="col-md-auto">
<div class="custom-control custom-radio">
<input type="radio" id="<% $Name %>-no" name="<% $Name %>" class="custom-control-input" value="0" <% defined $CurrentValue && !$CurrentValue? ' checked="checked"': '' |n %>>
<label class="custom-control-label" for="<% $Name %>-no"><&|/l&>No</&></label>
</div>
</div>
-% if ($Default) {
<div class="col-md-auto">
<div class="custom-control custom-radio">
<input type="radio" id="<% $Name %>-empty" name="<% $Name %>" class="custom-control-input" value="__empty_value__" <% !defined $CurrentValue? ' checked="checked"': '' |n %>>
<label class="custom-control-label" for="<% $Name %>-empty"><% $DefaultLabel %></label>
</div>
</div>
+% } else {
+ <div class="col-md-auto">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="<% $Name %>-no" name="<% $Name %>" class="custom-control-input" value="0" <% !$CurrentValue? ' checked="checked"': '' |n %>>
+ <label class="custom-control-label" for="<% $Name %>-no"><&|/l&>No</&></label>
+ </div>
+ </div>
% }
</div>
% }
commit baf23200cd9c98d5243da9d6f5e7fd4d34e405ad
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 18:59:45 2017 +0000
DatabaseSetting schema updates
diff --git a/etc/acl.Pg b/etc/acl.Pg
index d393a6f5cb..a48373ec6d 100644
--- a/etc/acl.Pg
+++ b/etc/acl.Pg
@@ -66,6 +66,8 @@ sub acl {
CustomRoles
objectcustomroles_id_seq
ObjectCustomRoles
+ databasesettings_id_seq
+ DatabaseSettings
);
my $db_user = RT->Config->Get('DatabaseUser');
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 102f134ddd..b4ce7404e4 100644
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -538,3 +538,20 @@ CREATE TABLE ObjectCustomRoles (
LastUpdated DATE
);
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
+
+CREATE SEQUENCE DatabaseSettings_seq;
+CREATE TABLE DatabaseSettings (
+ id NUMBER(11,0) CONSTRAINT DatabaseSettings_key PRIMARY KEY,
+ Name VARCHAR2(255) CONSTRAINT DatabaseSettings_Name_Unique unique NOT NULL,
+ Content CLOB,
+ ContentType VARCHAR2(80),
+ Disabled NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Created DATE,
+ LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
+ LastUpdated DATE
+);
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 3836c26670..8c44a8f853 100644
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -778,3 +778,21 @@ CREATE TABLE ObjectCustomRoles (
);
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
+
+CREATE SEQUENCE databasesettings_id_seq;
+CREATE TABLE DatabaseSettings (
+ id integer DEFAULT nextval('databasesettings_id_seq'),
+ Name varchar(255) NOT NULL,
+ Content text NULL,
+ ContentType varchar(80) NULL,
+ Disabled integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy integer NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL,
+ PRIMARY KEY (id)
+);
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index b6d2867274..64d395a866 100644
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -569,3 +569,19 @@ CREATE TABLE ObjectCustomRoles (
PRIMARY KEY (id)
);
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
+
+CREATE TABLE DatabaseSettings (
+ id INTEGER PRIMARY KEY,
+ Name varchar(255) collate NOCASE NOT NULL,
+ Content longtext collate NOCASE NULL,
+ ContentType varchar(80) collate NOCASE NULL,
+ Disabled int2 NOT NULL DEFAULT 0,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL
+);
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+
diff --git a/etc/schema.mysql b/etc/schema.mysql
index cb87d86a3a..f37469e89a 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -559,3 +559,20 @@ CREATE TABLE ObjectCustomRoles (
) ENGINE=InnoDB CHARACTER SET utf8;
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
+
+CREATE TABLE DatabaseSettings (
+ id int(11) NOT NULL AUTO_INCREMENT,
+ Name varchar(255) NOT NULL,
+ Content longblob NULL,
+ ContentType varchar(80) CHARACTER SET ascii NULL,
+ Disabled int2 NOT NULL DEFAULT 0,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created datetime DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated datetime DEFAULT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+
diff --git a/etc/upgrade/4.5.0/acl.Pg b/etc/upgrade/4.5.0/acl.Pg
new file mode 100644
index 0000000000..6a73f0cd45
--- /dev/null
+++ b/etc/upgrade/4.5.0/acl.Pg
@@ -0,0 +1,30 @@
+sub acl {
+ my $dbh = shift;
+
+ my @acls;
+ my @tables = qw (
+ databasesettings_id_seq
+ DatabaseSettings
+ );
+
+ my $db_user = RT->Config->Get('DatabaseUser');
+
+ my $sequence_right
+ = ( $dbh->{pg_server_version} >= 80200 )
+ ? "USAGE, SELECT, UPDATE"
+ : "SELECT, UPDATE";
+
+ foreach my $table (@tables) {
+ # Tables are upper-case, sequences are lowercase in @tables
+ if ( $table =~ /^[a-z]/ ) {
+ push @acls, "GRANT $sequence_right ON $table TO \"$db_user\";"
+ }
+ else {
+ push @acls, "GRANT SELECT, INSERT, UPDATE, DELETE ON $table TO \"$db_user\";"
+ }
+ }
+ return (@acls);
+}
+
+1;
+
diff --git a/etc/upgrade/4.5.0/schema.Oracle b/etc/upgrade/4.5.0/schema.Oracle
index b18ad9c979..17fed3dc1c 100644
--- a/etc/upgrade/4.5.0/schema.Oracle
+++ b/etc/upgrade/4.5.0/schema.Oracle
@@ -1 +1,16 @@
ALTER TABLE Classes DROP( HotList );
+CREATE SEQUENCE DatabaseSettings_seq;
+CREATE TABLE DatabaseSettings (
+ id NUMBER(11,0) CONSTRAINT DatabaseSettings_key PRIMARY KEY,
+ Name VARCHAR2(255) CONSTRAINT DatabaseSettings_Name_Unique unique NOT NULL,
+ Content CLOB,
+ ContentType VARCHAR2(80),
+ Disabled NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Created DATE,
+ LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
+ LastUpdated DATE
+);
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
diff --git a/etc/upgrade/4.5.0/schema.Pg b/etc/upgrade/4.5.0/schema.Pg
index f7563efc51..0efa1411d0 100644
--- a/etc/upgrade/4.5.0/schema.Pg
+++ b/etc/upgrade/4.5.0/schema.Pg
@@ -1 +1,17 @@
ALTER TABLE Classes DROP COLUMN HotList;
+CREATE SEQUENCE databasesettings_id_seq;
+CREATE TABLE DatabaseSettings (
+ id integer DEFAULT nextval('databasesettings_id_seq'),
+ Name varchar(255) NOT NULL,
+ Content text NULL,
+ ContentType varchar(80) NULL,
+ Disabled integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy integer NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL,
+ PRIMARY KEY (id)
+);
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
diff --git a/etc/upgrade/4.5.0/schema.SQLite b/etc/upgrade/4.5.0/schema.SQLite
new file mode 100644
index 0000000000..a25a8c9b49
--- /dev/null
+++ b/etc/upgrade/4.5.0/schema.SQLite
@@ -0,0 +1,15 @@
+CREATE TABLE DatabaseSettings (
+ id INTEGER PRIMARY KEY,
+ Name varchar(255) collate NOCASE NOT NULL,
+ Content longtext collate NOCASE NULL,
+ ContentType varchar(80) collate NOCASE NULL,
+ Disabled int2 NOT NULL DEFAULT 0,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL
+);
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+
diff --git a/etc/upgrade/4.5.0/schema.mysql b/etc/upgrade/4.5.0/schema.mysql
index f7563efc51..444678cfc4 100644
--- a/etc/upgrade/4.5.0/schema.mysql
+++ b/etc/upgrade/4.5.0/schema.mysql
@@ -1 +1,16 @@
ALTER TABLE Classes DROP COLUMN HotList;
+CREATE TABLE DatabaseSettings (
+ id int(11) NOT NULL AUTO_INCREMENT,
+ Name varchar(255) NOT NULL,
+ Content longblob NULL,
+ ContentType varchar(80) CHARACTER SET ascii NULL,
+ Disabled int2 NOT NULL DEFAULT 0,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created datetime DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated datetime DEFAULT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
+CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
commit a888aa6cb0fd3cf29102dc595b1d94ec026be3c1
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 19:01:37 2017 +0000
Add ORM classes for DatabaseSettings
diff --git a/lib/RT/DatabaseSetting.pm b/lib/RT/DatabaseSetting.pm
new file mode 100644
index 0000000000..185aae01f8
--- /dev/null
+++ b/lib/RT/DatabaseSetting.pm
@@ -0,0 +1,417 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+use 5.10.1;
+
+package RT::DatabaseSetting;
+use base 'RT::Record';
+
+use Storable ();
+use MIME::Base64;
+use JSON ();
+
+=head1 NAME
+
+RT::DatabaseSetting - Represents a config setting
+
+=cut
+
+=head1 METHODS
+
+=head2 Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database. Available
+keys are:
+
+=over 4
+
+=item Name
+
+Must be unique.
+
+=item Content
+
+If you provide a reference, we will automatically serialize the data structure
+using L<Storable>. Otherwise any string is passed through as-is.
+
+=item ContentType
+
+Currently handles C<storable> or C<application/json>.
+
+=back
+
+Returns a tuple of (status, msg) on failure and (id, msg) on success.
+Also automatically propagates this config change to all server processes.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Content => '',
+ ContentType => '',
+ @_,
+ );
+
+ return (0, $self->loc("Permission Denied"))
+ unless $self->CurrentUserHasRight('SuperUser');
+
+ unless ( $args{'Name'} ) {
+ return ( 0, $self->loc("Must specify 'Name' attribute") );
+ }
+
+ my ( $id, $msg ) = $self->ValidateName( $args{'Name'} );
+ return ( 0, $msg ) unless $id;
+
+ my $meta = RT->Config->Meta($args{'Name'});
+ if ($meta->{Immutable}) {
+ return ( 0, $self->loc("You cannot update [_1] using database config; you must edit your site config", $args{'Name'}) );
+ }
+
+ if (ref ($args{'Content'}) ) {
+ ($args{'Content'}, my $error) = $self->_SerializeContent($args{'Content'}, $args{'Name'});
+ if ($error) {
+ return (0, $error);
+ }
+ $args{'ContentType'} = 'storable';
+ }
+
+ my $old_value = RT->Config->Get($args{Name});
+ unless (defined($old_value) && length($old_value)) {
+ $old_value = $self->loc('(no value)');
+ }
+
+ ( $id, $msg ) = $self->SUPER::Create(
+ map { $_ => $args{$_} } grep {exists $args{$_}}
+ qw(Name Content ContentType),
+ );
+ unless ($id) {
+ return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $args{Name}, $args{Content}, $msg));
+ }
+
+ RT->Config->ApplyConfigChangeToAllServerProcesses;
+
+ my ($content, $error) = $self->Content;
+ unless (defined($content) && length($content)) {
+ $content = $self->loc('(no value)');
+ }
+
+ 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));
+ }
+}
+
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the database setting
+
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+
+ return $self->CurrentUserHasRight('SuperUser');
+}
+
+=head2 Load
+
+Load a setting from the database. Takes a single argument. If the
+argument is numerical, load by the column 'id'. Otherwise, load by the
+"Name" column.
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $identifier = shift || return undef;
+
+ if ( $identifier !~ /\D/ ) {
+ return $self->SUPER::LoadById( $identifier );
+ } else {
+ return $self->LoadByCol( "Name", $identifier );
+ }
+}
+
+=head2 SetName
+
+Not permitted
+
+=cut
+
+sub SetName {
+ my $self = shift;
+ return (0, $self->loc("Permission Denied"));
+}
+
+=head2 ValidateName
+
+Returns either (0, "failure reason") or 1 depending on whether the given
+name is valid.
+
+=cut
+
+sub ValidateName {
+ my $self = shift;
+ my $name = shift;
+
+ return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
+
+ my $TempSetting = RT::DatabaseSetting->new( RT->SystemUser );
+ $TempSetting->Load($name);
+
+ if ( $TempSetting->id && ( !$self->id || $TempSetting->id != $self->id ) ) {
+ return ( 0, $self->loc('Name in use') );
+ }
+ else {
+ return 1;
+ }
+}
+
+=head2 Delete
+
+Checks ACL, and on success propagates this config change to all server
+processes.
+
+=cut
+
+sub Delete {
+ my $self = shift;
+ return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
+ my ($ok, $msg) = $self->SUPER::Delete(@_);
+ return ($ok, $msg) if !$ok;
+ RT->Config->ApplyConfigChangeToAllServerProcesses;
+ RT->Logger->info($self->CurrentUser->Name . " removed database setting for " . $self->Name);
+ return ($ok, $self->loc("Database setting removed."));
+}
+
+=head2 DecodedContent
+
+Returns a pair of this setting's content and any error.
+
+=cut
+
+sub DecodedContent {
+ my $self = shift;
+
+ # Here we call _Value to run the ACL check.
+ my $content = $self->_Value('Content');
+
+ my $type = $self->__Value('ContentType') || '';
+
+ if ($type eq 'storable') {
+ return $self->_DeserializeContent($content);
+ }
+ elsif ($type eq 'application/json') {
+ return $self->_DeJSONContent($content);
+ }
+
+ return ($content, "");
+}
+
+=head2 SetContent
+
+=cut
+
+sub SetContent {
+ my $self = shift;
+ my $value = shift;
+ my $content_type = shift || '';
+
+ return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
+
+ my ($old_value, $error) = $self->Content;
+ unless (defined($old_value) && length($old_value)) {
+ $old_value = $self->loc('(no value)');
+ }
+
+ if (ref $value) {
+ ($value, my $error) = $self->_SerializeContent($value);
+ if ($error) {
+ return (0, $error);
+ }
+ $content_type = 'storable';
+ }
+
+ $RT::Handle->BeginTransaction;
+
+ my ($ok, $msg) = $self->_Set( Field => 'Content', Value => $value );
+ if (!$ok) {
+ $RT::Handle->Rollback;
+ return ($ok, $self->loc("Unable to update [_1]: [_2]", $self->Name, $msg));
+ }
+
+ if ($self->ContentType ne $content_type) {
+ ($ok, $msg) = $self->_Set( Field => 'ContentType', Value => $content_type );
+ if (!$ok) {
+ $RT::Handle->Rollback;
+ return ($ok, $self->loc("Unable to update [_1]: [_2]", $self->Name, $msg));
+ }
+ }
+
+ $RT::Handle->Commit;
+ RT->Config->ApplyConfigChangeToAllServerProcesses;
+
+ unless (defined($value) && length($value)) {
+ $value = $self->loc('(no value)');
+ }
+
+ 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));
+ }
+}
+
+=head1 PRIVATE METHODS
+
+Documented for internal use only, do not call these from outside
+RT::DatabaseSetting itself.
+
+=head2 _Set
+
+Checks if the current user has I<SuperUser> before calling
+C<SUPER::_Set>, and then propagates this config change to all server processes.
+
+=cut
+
+sub _Set {
+ my $self = shift;
+ my %args = (
+ Field => undef,
+ Value => undef,
+ @_
+ );
+
+ return (0, $self->loc("Permission Denied"))
+ unless $self->CurrentUserCanSee;
+
+ my ($ok, $msg) = $self->SUPER::_Set(@_);
+ RT->Config->ApplyConfigChangeToAllServerProcesses;
+ return ($ok, $msg);
+}
+
+=head2 _Value
+
+Checks L</CurrentUserCanSee> before calling C<SUPER::_Value>.
+
+=cut
+
+sub _Value {
+ my $self = shift;
+ return unless $self->CurrentUserCanSee;
+ return $self->SUPER::_Value(@_);
+}
+
+sub _SerializeContent {
+ my $self = shift;
+ my $content = shift;
+ my $name = shift || $self->Name;
+ my $frozen = eval { encode_base64(Storable::nfreeze($content)) };
+
+ 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;
+}
+
+sub _DeserializeContent {
+ my $self = shift;
+ my $content = shift;
+
+ my $thawed = eval { Storable::thaw(decode_base64($content)) };
+ 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;
+}
+
+sub Table { "DatabaseSettings" }
+
+sub _CoreAccessible {
+ {
+ id => { read => 1, type => 'int(11)', default => '' },
+ Name => { read => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
+ Content => { read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''},
+ ContentType => { read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
+ Disabled => { read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
+ Creator => { read => 1, type => 'int(11)', default => '0', auto => 1 },
+ Created => { read => 1, type => 'datetime', default => '', auto => 1 },
+ LastUpdatedBy => { read => 1, type => 'int(11)', default => '0', auto => 1 },
+ LastUpdated => { read => 1, type => 'datetime', default => '', auto => 1 },
+ }
+}
+
+1;
+
diff --git a/lib/RT/DatabaseSettings.pm b/lib/RT/DatabaseSettings.pm
new file mode 100644
index 0000000000..3236bad2c0
--- /dev/null
+++ b/lib/RT/DatabaseSettings.pm
@@ -0,0 +1,84 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::DatabaseSettings;
+use base 'RT::SearchBuilder';
+
+=head1 NAME
+
+RT::DatabaseSettings - a collection of L<RT::DatabaseSettings> objects
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return RT::DatabaseSetting->new( $self->CurrentUser );
+}
+
+=head2 _Init
+
+Sets default ordering by id ascending.
+
+=cut
+
+sub _Init {
+ my $self = shift;
+
+ $self->{'with_disabled_column'} = 1;
+
+ $self->OrderBy( FIELD => 'id', ORDER => 'ASC' );
+ return $self->SUPER::_Init( @_ );
+}
+
+sub Table { "DatabaseSettings" }
+
+1;
+
commit 05db0b9b3797b8bce45f4c42f9f6af94fa93d721
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 19:15:16 2017 +0000
Port database config loading and refreshing from extension
diff --git a/lib/RT.pm b/lib/RT.pm
index 52fa299189..c53cbb5e38 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -199,6 +199,7 @@ sub Init {
ConnectToDatabase();
InitSystemObjects();
InitClasses(%args);
+ RT->Config->LoadConfigFromDatabase();
InitLogging();
ProcessPreInitMessages();
InitPlugins();
@@ -503,6 +504,8 @@ sub InitClasses {
require RT::Asset;
require RT::Assets;
require RT::CustomFieldValues::Canonicalizer;
+ require RT::DatabaseSetting;
+ require RT::DatabaseSettings;
_BuildTableAttributes();
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index ab42501fdf..4e2a243d4f 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -55,6 +55,7 @@ use 5.010;
use File::Spec ();
use Symbol::Global::Name;
use List::MoreUtils 'uniq';
+use Storable;
# Store log messages generated before RT::Logger is available
our @PreInitLoggerMessages;
@@ -1859,6 +1860,125 @@ sub EnableExternalAuth {
return;
}
+my $database_config_cache_time = 0;
+my %original_setting_from_files;
+my $in_config_change_txn = 0;
+
+sub BeginDatabaseConfigChanges {
+ $in_config_change_txn = $in_config_change_txn + 1;
+}
+
+sub EndDatabaseConfigChanges {
+ $in_config_change_txn = $in_config_change_txn - 1;
+ if (!$in_config_change_txn) {
+ shift->ApplyConfigChangeToAllServerProcesses();
+ }
+}
+
+sub ApplyConfigChangeToAllServerProcesses {
+ my $self = shift;
+
+ return if $in_config_change_txn;
+
+ # first apply locally
+ $self->LoadConfigFromDatabase();
+
+ # then notify other servers
+ RT->System->ConfigCacheNeedsUpdate($database_config_cache_time);
+}
+
+sub RefreshConfigFromDatabase {
+ my $self = shift;
+ if ($in_config_change_txn) {
+ RT->Logger->error("It appears that there were unbalanced calls to BeginDatabaseConfigChanges with EndDatabaseConfigChanges; this indicates a software fault");
+ $in_config_change_txn = 0;
+ }
+
+ my $needs_update = RT->System->ConfigCacheNeedsUpdate;
+ if ($needs_update > $database_config_cache_time) {
+ $self->LoadConfigFromDatabase();
+ $database_config_cache_time = $needs_update;
+ }
+}
+
+sub LoadConfigFromDatabase {
+ my $self = shift;
+
+ $database_config_cache_time = time;
+
+ my $settings = RT::DatabaseSettings->new(RT->SystemUser);
+ $settings->UnLimit;
+
+ my %seen;
+
+ while (my $setting = $settings->Next) {
+ my $name = $setting->Name;
+ my ($value, $error) = $setting->DecodedContent;
+ next if $error;
+
+ if (!exists $original_setting_from_files{$name}) {
+ $original_setting_from_files{$name} = [
+ scalar($self->Get($name)),
+ Storable::dclone(scalar($self->Meta($name))),
+ ];
+ }
+
+ $seen{$name}++;
+
+ # are we inadvertantly overriding RT_SiteConfig.pm?
+ my $meta = $META{$name};
+ if ($meta->{'Source'}) {
+ my %source = %{ $meta->{'Source'} };
+ if ($source{'SiteConfig'} && $source{'File'} ne 'database') {
+ warn("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 ($type eq 'HASH') {
+ $self->Set($name, ());
+ }
+
+ my $val = $type eq 'ARRAY' ? $value
+ : $type eq 'HASH' ? [ %$value ]
+ : [ $value ];
+
+ $self->SetFromConfig(
+ Option => \$name,
+ Value => $val,
+ Package => 'N/A',
+ File => 'database',
+ Line => 'N/A',
+ 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') {
+ $self->Set($name, @$value);
+ }
+ elsif ($type eq 'HASH') {
+ $self->Set($name, %$value);
+ }
+ else {
+ $self->Set($name, $value);
+ }
+
+ %{ $META{$name} } = %$meta;
+ }
+}
+
RT::Base->_ImportOverlays();
1;
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index d147ae8f8e..39eebb07e8 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -272,6 +272,8 @@ sub HandleRequest {
Module::Refresh->refresh;
}
+ RT->Config->RefreshConfigFromDatabase();
+
$HTML::Mason::Commands::r->content_type("text/html; charset=utf-8");
$HTML::Mason::Commands::m->{'rt_base_time'} = [ Time::HiRes::gettimeofday() ];
diff --git a/lib/RT/System.pm b/lib/RT/System.pm
index 7670b91fb3..e7b761eda1 100644
--- a/lib/RT/System.pm
+++ b/lib/RT/System.pm
@@ -236,6 +236,27 @@ sub CustomRoleCacheNeedsUpdate {
}
}
+=head2 ConfigCacheNeedsUpdate ( 1 )
+
+Attribute to decide when we need to flush the database settings
+and re-register any changes. Set when settings are created, enabled/disabled, etc.
+
+If passed a true value, will update the attribute to be the current time.
+
+=cut
+
+sub ConfigCacheNeedsUpdate {
+ my $self = shift;
+ my $time = shift;
+
+ if ($time) {
+ return $self->SetAttribute(Name => 'ConfigCacheNeedsUpdate', Content => $time);
+ } else {
+ my $cache = $self->FirstAttribute('ConfigCacheNeedsUpdate');
+ return (defined $cache ? $cache->Content : 0 );
+ }
+}
+
=head2 AddUpgradeHistory package, data
Adds an entry to the upgrade history database. The package can be either C<RT>
commit 7648dd10f1ac6c2ba6413415a850de4fe6f6f467
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 19:17:05 2017 +0000
Add Code and MultilineString widgets
diff --git a/share/html/Widgets/Form/Code b/share/html/Widgets/Form/Code
new file mode 100644
index 0000000000..1c346d6c18
--- /dev/null
+++ b/share/html/Widgets/Form/Code
@@ -0,0 +1,57 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%DOC>
+see docs/extending/using_forms_widgets.pod
+</%DOC>
+<& /Widgets/Form/MultilineString, Class => 'code', %ARGS &>
+<%METHOD InputOnly>
+<& /Widgets/Form/MultilineString:InputOnly, %ARGS &>
+</%METHOD>
+<%METHOD Process>
+<& /Widgets/Form/MultilineString:Process, %ARGS &>
+</%METHOD>
diff --git a/share/html/Widgets/Form/MultilineString b/share/html/Widgets/Form/MultilineString
new file mode 100644
index 0000000000..fa6a74758d
--- /dev/null
+++ b/share/html/Widgets/Form/MultilineString
@@ -0,0 +1,102 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%DOC>
+see docs/extending/using_forms_widgets.pod
+</%DOC>
+<div id="form-box-<% lc $Name %>" class="widget <% $Class %>">
+<span class="description label"><% $Description %></span>
+<span class="value"><& SELF:InputOnly, %ARGS &></span>
+% if ( $Default ) {
+<span class="comment"><% $DefaultLabel %></span>
+% }
+<span class="hints"><% $Hints %></span>
+</div>
+<%ARGS>
+$Name
+
+$Class => ''
+$Description => undef,
+$Hints => ''
+
+$CurrentValue => '',
+
+$Default => 0,
+$DefaultValue => '',
+$DefaultLabel => loc( 'Default: [_1]', $DefaultValue ),
+</%ARGS>
+
+<%METHOD InputOnly>
+<textarea name="<% $Name %>" cols="<% $Cols %>" rows="<% $Rows %>"><% $CurrentValue %></textarea>
+<%ARGS>
+$Name
+$Cols => 80
+$Rows => 6
+$CurrentValue => '',
+</%ARGS>
+</%METHOD>
+
+<%METHOD Process>
+<%ARGS>
+$Name
+
+$Arguments => {},
+
+$Default => 0,
+$DefaultValue => '',
+</%ARGS>
+<%INIT>
+my $value = $Arguments->{ $Name };
+$value = '' unless defined $value;
+
+if ( $value eq '' ) {
+ return $DefaultValue unless $Default;
+ return undef;
+}
+return $value;
+</%INIT>
+</%METHOD>
commit 21f6abfb8cc62adb883f9045121439afb170d740
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 19:23:14 2017 +0000
Port EditConfig page from extension
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 0e1d7b9c36..df2dd9be5a 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -983,6 +983,11 @@ sub _BuildAdminMenu {
description => loc('Detailed information about your RT setup'),
path => '/Admin/Tools/Configuration.html',
);
+ $admin_tools->child( edit_config =>
+ title => loc('Edit Configuration'),
+ description => loc('Update your RT setup'),
+ path => '/Admin/Tools/EditConfig.html',
+ );
$admin_tools->child( theme =>
title => loc('Theme'),
description => loc('Customize the look of your RT'),
diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
new file mode 100644
index 0000000000..c3185edf47
--- /dev/null
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -0,0 +1,270 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%INIT>
+my $title = loc('System Configuration');
+unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser')) {
+ 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;
+$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;
+my $stringify = sub {
+ my $value = shift;
+ return "" if !defined($value);
+
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Indent = 2;
+ local $Data::Dumper::Sortkeys = 1;
+ my $output = Dumper $value;
+ chomp $output;
+ return $output;
+};
+
+if (delete $ARGS{Update}) {
+ RT->Config->BeginDatabaseConfigChanges;
+ $RT::Handle->BeginTransaction;
+ my $has_error;
+
+ 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 ( $meta->{Immutable} || $meta->{Obfuscate} || ($key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ )) {
+ push @results, loc("Cannot change [_1]: Permission Denied", $key);
+ $has_error++;
+ next;
+ }
+
+ if ($is_code) {
+ if (!$has_execute_code) {
+ 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 ($coderef) {
+ $val = eval { $coderef->() };
+ if ($@) {
+ my $error = $@;
+ push @results, loc("Couldn't execute [_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);
+ }
+
+ 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) {
+ push @results, loc("No changes made.");
+ $RT::Handle->Rollback;
+ }
+ else {
+ $RT::Handle->Commit;
+ }
+ RT->Config->EndDatabaseConfigChanges;
+}
+
+</%INIT>
+<& /Admin/Elements/Header, Title => $title &>
+<& /Elements/Tabs &>
+<& /Elements/ListActions, actions => \@results &>
+
+<form id="EditConfig" method="post" action="EditConfig.html">
+<input type="hidden" name="Update" value=1></input>
+
+<&|/Widgets/TitleBox, title => loc("RT Configuration") &>
+<table border="0" cellspacing="0" cellpadding="5" width="100%" class="collection">
+<tr class="collection-as-table">
+<th class="collection-as-table"><&|/l&>Option</&></th>
+<th class="collection-as-table"><&|/l&>Value</&></th>
+</tr>
+<%PERL>
+my $index_conf;
+foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
+ my $meta = RT->Config->Meta( $key );
+
+ next if $meta->{Invisible};
+
+ my $raw_value = RT->Config->Get( $key );
+ my $val = $stringify->($raw_value);
+
+ $index_conf++;
+
+ 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 $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'} || {};
+
+ if ($widget eq '/Widgets/Form/Boolean') {
+ %$args = (
+ Default => 0,
+ RadioStyle => 1,
+ %$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>
+<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 ( $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>
+% } 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 {
+<input type="text" disabled width="80" value="<% $current_value %>"></input>
+% }
+<br><em><% loc('Must modify in config file' ) %></em>
+% } else {
+ <& $widget,
+ Default => 1,
+ DefaultValue => '',
+ DefaultLabel => '(no value)',
+
+ %{ $m->comp('/Widgets/FinalizeWidgetArguments', WidgetArguments => $args ) },
+ Name => $key,
+ CurrentValue => $current_value,
+ Description => '',
+ Hints => '',
+ &>
+<textarea class="hidden" name="<% $key %>-Current"><% $current_value %></textarea>
+% }
+</td>
+</tr>
+% }
+</table>
+</&>
+<& /Elements/Submit, Label => loc('Save Changes') &>
+</form>
+
diff --git a/share/static/css/elevator-light/forms.css b/share/static/css/elevator-light/forms.css
index 7c2c122669..2554a2afa9 100644
--- a/share/static/css/elevator-light/forms.css
+++ b/share/static/css/elevator-light/forms.css
@@ -363,3 +363,23 @@ ul li .dropdown-item.active span,
ul li .dropdown-item:active span {
color: #fff;
}
+
+/* remove unnecessary left padding for radio options */
+#EditConfig div.widget .label {
+ width: auto;
+ float: none;
+}
+
+#EditConfig textarea:disabled,
+#EditConfig input:disabled {
+ background-color: #EEE;
+}
+
+.widget.code textarea,
+textarea.code {
+ font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
+}
+
+#EditConfig ul.plugins {
+ margin: 0;
+}
commit 165422a396141d9f2ac4fb83bef0c5acef37545b
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 19:33:14 2017 +0000
Annotate Immutable options
These options cannot be changed in database settings
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 4e2a243d4f..30c81ee72b 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -638,6 +638,7 @@ our %META;
# this tends to break extensions that stash links in ticket update pages
Organization => {
Type => 'SCALAR',
+ Immutable => 1,
PostLoadCheck => sub {
my ($self,$value) = @_;
$RT::Logger->error("your \$Organization setting ($value) appears to contain whitespace. Please fix this.")
@@ -645,9 +646,38 @@ our %META;
},
},
+ rtname => {
+ Immutable => 1,
+ },
+
# Internal config options
DatabaseExtraDSN => {
- Type => 'HASH',
+ Type => 'HASH',
+ Immutable => 1,
+ },
+ DatabaseAdmin => {
+ Immutable => 1,
+ },
+ DatabaseHost => {
+ Immutable => 1,
+ },
+ DatabaseName => {
+ Immutable => 1,
+ },
+ DatabasePassword => {
+ Immutable => 1,
+ },
+ DatabasePort => {
+ Immutable => 1,
+ },
+ DatabaseRTHost => {
+ Immutable => 1,
+ },
+ DatabaseType => {
+ Immutable => 1,
+ },
+ DatabaseUser => {
+ Immutable => 1,
},
FullTextSearch => {
@@ -741,8 +771,24 @@ our %META;
Type => 'SCALAR',
PostLoadCheck => sub { RT::Interface::Email->_HTMLFormatter },
},
+ Plugins => {
+ Immutable => 1,
+ },
+ RecordBaseClass => {
+ Immutable => 1,
+ },
+ WebSessionClass => {
+ Immutable => 1,
+ },
+ DevelMode => {
+ Immutable => 1,
+ },
+ DisallowExecuteCode => {
+ Immutable => 1,
+ },
MailPlugins => {
Type => 'ARRAY',
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
@@ -868,6 +914,7 @@ our %META;
EmailDashboardLanguageOrder => { Type => 'ARRAY' },
CustomFieldValuesCanonicalizers => { Type => 'ARRAY' },
WebPath => {
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -894,6 +941,7 @@ our %META;
},
},
WebDomain => {
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -920,6 +968,7 @@ our %META;
},
},
WebPort => {
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -935,6 +984,7 @@ our %META;
},
},
WebBaseURL => {
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -958,6 +1008,7 @@ our %META;
},
},
WebURL => {
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -1103,7 +1154,12 @@ our %META;
},
},
+ ExternalAuth => {
+ Immutable => 1,
+ },
+
ExternalSettings => {
+ Immutable => 1,
Obfuscate => sub {
# Ensure passwords are obfuscated on the System Configuration page
my ($config, $sources, $user) = @_;
@@ -1167,6 +1223,7 @@ our %META;
},
ExternalAuthPriority => {
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
my @values = @{ shift || [] };
@@ -1195,6 +1252,7 @@ our %META;
},
ExternalInfoPriority => {
+ Immutable => 1,
PostLoadCheck => sub {
my $self = shift;
my @values = @{ shift || [] };
commit f2f0b772d772f212355e3d7df644c2f30efefe59
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 19:40:29 2017 +0000
Add widget metadata for config options
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 30c81ee72b..5d365fec6a 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -639,6 +639,7 @@ our %META;
Organization => {
Type => 'SCALAR',
Immutable => 1,
+ Widget => '/Widgets/Form/String',
PostLoadCheck => sub {
my ($self,$value) = @_;
$RT::Logger->error("your \$Organization setting ($value) appears to contain whitespace. Please fix this.")
@@ -648,6 +649,7 @@ our %META;
rtname => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
# Internal config options
@@ -657,27 +659,35 @@ our %META;
},
DatabaseAdmin => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
DatabaseHost => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
DatabaseName => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
DatabasePassword => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
DatabasePort => {
Immutable => 1,
+ Widget => '/Widgets/Form/Integer',
},
DatabaseRTHost => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
DatabaseType => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
DatabaseUser => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
FullTextSearch => {
@@ -736,6 +746,7 @@ our %META;
},
DisableGraphViz => {
Type => 'SCALAR',
+ Widget => '/Widgets/Form/Boolean',
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -747,6 +758,7 @@ our %META;
},
DisableGD => {
Type => 'SCALAR',
+ Widget => '/Widgets/Form/Boolean',
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -758,6 +770,7 @@ our %META;
},
MailCommand => {
Type => 'SCALAR',
+ Widget => '/Widgets/Form/String',
PostLoadCheck => sub {
my $self = shift;
my $value = $self->Get('MailCommand');
@@ -769,6 +782,7 @@ our %META;
},
HTMLFormatter => {
Type => 'SCALAR',
+ Widget => '/Widgets/Form/String',
PostLoadCheck => sub { RT::Interface::Email->_HTMLFormatter },
},
Plugins => {
@@ -776,15 +790,19 @@ our %META;
},
RecordBaseClass => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
WebSessionClass => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
},
DevelMode => {
Immutable => 1,
+ Widget => '/Widgets/Form/Boolean',
},
DisallowExecuteCode => {
Immutable => 1,
+ Widget => '/Widgets/Form/Boolean',
},
MailPlugins => {
Type => 'ARRAY',
@@ -915,6 +933,7 @@ our %META;
CustomFieldValuesCanonicalizers => { Type => 'ARRAY' },
WebPath => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -941,7 +960,8 @@ our %META;
},
},
WebDomain => {
- Immutable => 1,
+ Immutable => 1,
+ Widget => '/Widgets/Form/String',
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -969,6 +989,7 @@ our %META;
},
WebPort => {
Immutable => 1,
+ Widget => '/Widgets/Form/Integer',
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -985,6 +1006,7 @@ our %META;
},
WebBaseURL => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -1009,6 +1031,7 @@ our %META;
},
WebURL => {
Immutable => 1,
+ Widget => '/Widgets/Form/String',
PostLoadCheck => sub {
my $self = shift;
my $value = shift;
@@ -1156,6 +1179,7 @@ our %META;
ExternalAuth => {
Immutable => 1,
+ Widget => '/Widgets/Form/Boolean',
},
ExternalSettings => {
@@ -1286,7 +1310,6 @@ our %META;
$self->Set( 'ExternalInfoPriority', \@values );
},
},
-
ServiceBusinessHours => {
Type => 'HASH',
PostLoadCheck => sub {
@@ -1299,10 +1322,369 @@ our %META;
}
},
},
-
ServiceAgreements => {
Type => 'HASH',
},
+ AllowUserAutocompleteForUnprivileged => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ AlwaysDownloadAttachments => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ AmbiguousDayInFuture => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ AmbiguousDayInPast => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ApprovalRejectionNotes => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ArticleOnTicketCreate => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ AutocompleteOwnersForSearch => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ CanonicalizeRedirectURLs => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ CanonicalizeURLsInFeeds => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ChartsTimezonesInDB => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ CheckMoreMSMailHeaders => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ DateDayBeforeMonth => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ DontSearchFileAttachments => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ DropLongAttachments => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ EditCustomFieldsSingleColumn => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ EnableReminders => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ExternalStorageDirectLink => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ForceApprovalsView => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ForwardFromUser => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ Framebusting => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ HideArticleSearchOnReplyCreate => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ HideResolveActionsWithDependencies => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ HideTimeFieldsFromUnprivilegedUsers => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ LoopsToRTOwner => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ MessageBoxIncludeSignature => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ MessageBoxIncludeSignatureOnComment => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ OnlySearchActiveTicketsInSimpleSearch => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ParseNewMessageForTicketCcs => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ PreferDateTimeFormatNatural => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ PreviewScripMessages => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ RecordOutgoingEmail => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ RestrictLoginReferrer => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ RestrictReferrer => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ SearchResultsAutoRedirect => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ShowBccHeader => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ShowMoreAboutPrivilegedUsers => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ShowRTPortal => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ShowRemoteImages => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ShowTransactionImages => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ StoreLoops => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ StrictLinkACL => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ SuppressInlineTextFiles => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ TruncateLongAttachments => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ TrustHTMLAttachments => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ UseFriendlyFromLine => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ UseFriendlyToLine => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ UseOriginatorHeader => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ UseSQLForACLChecks => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ UseTransactionBatch => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ValidateUserEmailAddresses => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ VERPPrefix => {
+ Widget => '/Widgets/Form/String',
+ WidgetArguments => { Hints => 'rt-', },
+ },
+ VERPDomain => {
+ Widget => '/Widgets/Form/String',
+ WidgetArguments => {
+ Callback => sub { return { Hints => RT->Config->Get( 'Organization') } },
+ },
+ },
+ WebFallbackToRTLogin => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WebFlushDbCacheEveryRequest => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WebHttpOnlyCookies => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WebRemoteUserAuth => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WebRemoteUserAutocreate => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WebRemoteUserContinuous => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WebRemoteUserGecos => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WebSecureCookies => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ WikiImplicitLinks => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ HideOneTimeSuggestions => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ LinkArticlesOnInclude => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ SelfServiceCorrespondenceOnly => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+ ShowSearchResultCount => {
+ Widget => '/Widgets/Form/Boolean',
+ },
+
+ AttachmentListCount => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ AutoLogoff => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ BcryptCost => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ DefaultSummaryRows => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ ExternalStorageCutoffSize => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ LogoutRefresh => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ MaxAttachmentSize => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ MaxFulltextAttachmentSize => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ MinimumPasswordLength => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ MoreAboutRequestorGroupsLimit => {
+ Widget => '/Widgets/Form/Integer',
+ },
+ TicketsItemMapSize => {
+ Widget => '/Widgets/Form/Integer',
+ },
+
+ CommentAddress => {
+ Widget => '/Widgets/Form/String',
+ },
+ CorrespondAddress => {
+ Widget => '/Widgets/Form/String',
+ },
+ DashboardAddress => {
+ Widget => '/Widgets/Form/String',
+ },
+ DashboardSubject => {
+ Widget => '/Widgets/Form/String',
+ },
+ DefaultErrorMailPrecedence => {
+ Widget => '/Widgets/Form/String',
+ },
+ DefaultMailPrecedence => {
+ Widget => '/Widgets/Form/String',
+ },
+ DefaultSearchResultOrderBy => {
+ Widget => '/Widgets/Form/String',
+ },
+ EmailOutputEncoding => {
+ Widget => '/Widgets/Form/String',
+ },
+ FriendlyFromLineFormat => {
+ Widget => '/Widgets/Form/String',
+ },
+ FriendlyToLineFormat => {
+ Widget => '/Widgets/Form/String',
+ },
+ LogDir => {
+ Widget => '/Widgets/Form/String',
+ },
+ LogToFileNamed => {
+ Widget => '/Widgets/Form/String',
+ },
+ LogoAltText => {
+ Widget => '/Widgets/Form/String',
+ },
+ LogoLinkURL => {
+ Widget => '/Widgets/Form/String',
+ },
+ LogoURL => {
+ Widget => '/Widgets/Form/String',
+ },
+ OwnerEmail => {
+ Widget => '/Widgets/Form/String',
+ },
+ RedistributeAutoGeneratedMessages => {
+ Widget => '/Widgets/Form/String',
+ },
+ SendmailArguments => {
+ Widget => '/Widgets/Form/String',
+ },
+ SendmailBounceArguments => {
+ Widget => '/Widgets/Form/String',
+ },
+ SendmailPath => {
+ Widget => '/Widgets/Form/String',
+ },
+ SetOutgoingMailFrom => {
+ Widget => '/Widgets/Form/String',
+ },
+ Timezone => {
+ Widget => '/Widgets/Form/String',
+ },
+ WebImagesURL => {
+ Widget => '/Widgets/Form/String',
+ },
+
+ AssetSearchFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ AssetSummaryFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ AssetSummaryRelatedTicketsFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ DefaultSearchResultFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ DefaultSelfServiceSearchResultFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ MoreAboutRequestorExtraInfo => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ MoreAboutRequestorTicketListFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ UserSearchResultFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ UserSummaryExtraInfo => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+ UserSummaryTicketListFormat => {
+ Widget => '/Widgets/Form/MultilineString',
+ },
+
+ LogToSyslog => {
+ Widget => '/Widgets/Form/Select',
+ WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] },
+ },
+ LogToSTDERR => {
+ Widget => '/Widgets/Form/Select',
+ WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] },
+ },
+ LogToFile => {
+ Widget => '/Widgets/Form/Select',
+ WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] },
+ },
+ LogStackTraces => {
+ Widget => '/Widgets/Form/Select',
+ WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] },
+ },
+ StatementLog => {
+ Widget => '/Widgets/Form/Select',
+ WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] },
+ },
+
+ DefaultSearchResultOrder => {
+ Widget => '/Widgets/Form/Select',
+ WidgetArguments => { Values => [qw(ASC DESC)] },
+ },
);
my %OPTIONS = ();
my @LOADED_CONFIGS = ();
commit 5803325e5fb9f8956167427659a10a079c3608c3
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 19:54:10 2017 +0000
Hide deprecated options
diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
index c3185edf47..c606d5e0d8 100644
--- a/share/html/Admin/Tools/EditConfig.html
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -189,7 +189,7 @@ my $index_conf;
foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
my $meta = RT->Config->Meta( $key );
- next if $meta->{Invisible};
+ next if $meta->{Invisible} || $meta->{Deprecated};
my $raw_value = RT->Config->Get( $key );
my $val = $stringify->($raw_value);
commit b878fb9786a89b2e390114178f82c1b0a4d80e2c
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Aug 15 20:08:02 2017 +0000
List Database as source of configuration on Sys Config page
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 5d365fec6a..4252fbcb74 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -2142,7 +2142,7 @@ sub SetFromConfig {
}
$META{$name}->{'Type'} = $type;
- foreach (qw(Package File Line SiteConfig Extension)) {
+ foreach (qw(Package File Line SiteConfig Extension Database)) {
$META{$name}->{'Source'}->{$_} = $args{$_};
}
$self->Set( $name, @{ $args{'Value'} } );
@@ -2392,6 +2392,7 @@ sub LoadConfigFromDatabase {
Package => 'N/A',
File => 'database',
Line => 'N/A',
+ Database => 1,
SiteConfig => 1,
);
}
diff --git a/share/html/Admin/Tools/Configuration.html b/share/html/Admin/Tools/Configuration.html
index baf0abf027..0f34470a5b 100644
--- a/share/html/Admin/Tools/Configuration.html
+++ b/share/html/Admin/Tools/Configuration.html
@@ -70,7 +70,10 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
my $meta = RT->Config->Meta( $key );
my $description = '';
- if ( $meta->{'Source'}{'Extension'} && $meta->{'Source'}{'SiteConfig'} ) {
+ if ( $meta->{'Source'}{'Database'}) {
+ $description = loc("database");
+ }
+ elsif ( $meta->{'Source'}{'Extension'} && $meta->{'Source'}{'SiteConfig'} ) {
$description = loc("[_1] site config", $meta->{'Source'}{'Extension'});
}
elsif ( $meta->{'Source'}{'Extension'} ) {
commit 02b6a1a9fb1afbcb06e694c642dd4dbf256a62b4
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Sep 5 14:56:57 2017 -0400
Render config with EditLink as readonly
That way you can still easily export the settings to another RT
diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
index c606d5e0d8..62adc78734 100644
--- a/share/html/Admin/Tools/EditConfig.html
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -229,6 +229,11 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
<td class="collection-as-table">
% if ( $meta->{EditLink} ) {
+% if ($widget eq '/Widgets/Form/MultilineString' || $widget eq '/Widgets/Form/Code') {
+<textarea disabled class="<% $is_code ? 'code' : '' %>" rows="6" cols="80"><% $current_value %></textarea><br>
+% } else {
+<input type="text" disabled width="80" value="<% $current_value %>"></input><br>
+% }
<&|/l_unsafe, "<a href=\"$meta->{EditLink}\">", loc($meta->{EditLinkLabel}), "</a>" &>Visit [_1][_2][_3] to manage this setting</&>
% } elsif ( $key =~ /Plugins/) {
<ul class="plugins">
commit 94cbaf03d29977a212883a978ce33d7aa6f0a624
Author: michel <michel at bestpractical.com>
Date: Thu Oct 24 18:40:45 2019 +0200
Switch from Storable::dclone to Clone::clone to handle code/regex
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 4252fbcb74..08eee2a89b 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -55,7 +55,7 @@ use 5.010;
use File::Spec ();
use Symbol::Global::Name;
use List::MoreUtils 'uniq';
-use Storable;
+use Clone ();
# Store log messages generated before RT::Logger is available
our @PreInitLoggerMessages;
@@ -2004,7 +2004,6 @@ sub GetObfuscated {
return $self->Get(@_) unless $obfuscate;
- require Clone;
my $res = Clone::clone( $self->Get( @_ ) );
$res = $obfuscate->( $self, $res, $user );
return $self->_ReturnValue( $res, $META{$name}->{'Type'} || 'SCALAR' );
@@ -2359,7 +2358,7 @@ sub LoadConfigFromDatabase {
if (!exists $original_setting_from_files{$name}) {
$original_setting_from_files{$name} = [
scalar($self->Get($name)),
- Storable::dclone(scalar($self->Meta($name))),
+ Clone::clone(scalar($self->Meta($name))),
];
}
commit c0d5683bfb5117a7da125bd9f06864d37a3f855c
Author: sunnavy <sunnavy at bestpractical.com>
Date: Mon Nov 18 21:54:28 2019 +0800
Migrate MultilineString to new themes
diff --git a/share/html/Widgets/Form/MultilineString b/share/html/Widgets/Form/MultilineString
index fa6a74758d..12f7356138 100644
--- a/share/html/Widgets/Form/MultilineString
+++ b/share/html/Widgets/Form/MultilineString
@@ -48,13 +48,16 @@
<%DOC>
see docs/extending/using_forms_widgets.pod
</%DOC>
-<div id="form-box-<% lc $Name %>" class="widget <% $Class %>">
-<span class="description label"><% $Description %></span>
-<span class="value"><& SELF:InputOnly, %ARGS &></span>
+<div id="form-box-<% lc $Name %>" class="widget form-row <% $Class %>">
+ <div class="col-md-3 label">
+ <% $Description %>
+ </div>
+ <div class="col-md-9 value"><& SELF:InputOnly, %ARGS &>
% if ( $Default ) {
-<span class="comment"><% $DefaultLabel %></span>
+ <span class="comment"><% $DefaultLabel %></span>
% }
-<span class="hints"><% $Hints %></span>
+ <span class="hints"><% $Hints %></span>
+ </div>
</div>
<%ARGS>
$Name
@@ -71,7 +74,7 @@ $DefaultLabel => loc( 'Default: [_1]', $DefaultValue ),
</%ARGS>
<%METHOD InputOnly>
-<textarea name="<% $Name %>" cols="<% $Cols %>" rows="<% $Rows %>"><% $CurrentValue %></textarea>
+<textarea name="<% $Name %>" class="form-control" cols="<% $Cols %>" rows="<% $Rows %>"><% $CurrentValue %></textarea>
<%ARGS>
$Name
$Cols => 80
commit 9fcde4980af72e5a5d13a45419a5091e1e7014c6
Author: sunnavy <sunnavy at bestpractical.com>
Date: Mon Nov 18 21:52:00 2019 +0800
Add LabelLink support for form widgets
diff --git a/share/html/Widgets/Form/Boolean b/share/html/Widgets/Form/Boolean
index 3b7091b753..c965537d29 100644
--- a/share/html/Widgets/Form/Boolean
+++ b/share/html/Widgets/Form/Boolean
@@ -50,7 +50,11 @@ see docs/extending/using_forms_widgets.pod
</%DOC>
<div id="form-box-<% lc $Name %>" class="widget form-row">
<div class="col-md-3 label">
- <% $Description // '' %>
+% if( $LabelLink ) {
+ <a href="<% $LabelLink %>"><% $Description %></a>
+% } else {
+ <% $Description %>
+% }
</div>
<div class="col-md-9 value">
<& SELF:InputOnly, %ARGS &>
@@ -61,6 +65,7 @@ see docs/extending/using_forms_widgets.pod
$Name => undef,
$Description => undef,
$Hints => ''
+$LabelLink => ''
</%ARGS>
<%METHOD InputOnly>
diff --git a/share/html/Widgets/Form/Integer b/share/html/Widgets/Form/Integer
index 918fe88b0e..82d6206cc9 100644
--- a/share/html/Widgets/Form/Integer
+++ b/share/html/Widgets/Form/Integer
@@ -50,7 +50,11 @@ see docs/extending/using_forms_widgets.pod
</%DOC>
<div id="form-box-<% lc $Name %>" class="widget form-row">
<div class="col-md-3 label">
- <% $Description // '' %>
+% if( $LabelLink ) {
+ <a href="<% $LabelLink %>"><% $Description %></a>
+% } else {
+ <% $Description %>
+% }
</div>
<div class="col-md-9 value">
<& SELF:InputOnly, %ARGS &>
@@ -75,6 +79,7 @@ $CurrentValue => '',
$Default => 0,
$DefaultValue => 0,
$DefaultLabel => undef
+$LabelLink => ''
</%ARGS>
<%METHOD InputOnly>
diff --git a/share/html/Widgets/Form/MultilineString b/share/html/Widgets/Form/MultilineString
index 12f7356138..89106cdb39 100644
--- a/share/html/Widgets/Form/MultilineString
+++ b/share/html/Widgets/Form/MultilineString
@@ -50,7 +50,11 @@ see docs/extending/using_forms_widgets.pod
</%DOC>
<div id="form-box-<% lc $Name %>" class="widget form-row <% $Class %>">
<div class="col-md-3 label">
+% if( $LabelLink ) {
+ <a href="<% $LabelLink %>"><% $Description %></a>
+% } else {
<% $Description %>
+% }
</div>
<div class="col-md-9 value"><& SELF:InputOnly, %ARGS &>
% if ( $Default ) {
@@ -71,6 +75,7 @@ $CurrentValue => '',
$Default => 0,
$DefaultValue => '',
$DefaultLabel => loc( 'Default: [_1]', $DefaultValue ),
+$LabelLink => ''
</%ARGS>
<%METHOD InputOnly>
diff --git a/share/html/Widgets/Form/Select b/share/html/Widgets/Form/Select
index 5f8e3e6147..0711a50cf2 100644
--- a/share/html/Widgets/Form/Select
+++ b/share/html/Widgets/Form/Select
@@ -50,7 +50,11 @@ see docs/extending/using_forms_widgets.pod
</%DOC>
<div id="form-box-<% lc $Name %>" class="widget form-row">
<div class="col-md-3 label">
- <% $Description // '' %>
+% if( $LabelLink ) {
+ <a href="<% $LabelLink %>"><% $Description %></a>
+% } else {
+ <% $Description %>
+% }
</div>
<div class="col-md-9 value">
<& SELF:InputOnly, %ARGS &>
@@ -61,6 +65,7 @@ see docs/extending/using_forms_widgets.pod
$Name
$Description => undef,
$Hints => ''
+$LabelLink => ''
</%ARGS>
<%METHOD InputOnly>
diff --git a/share/html/Widgets/Form/String b/share/html/Widgets/Form/String
index 24cada9716..dc2c7514ca 100644
--- a/share/html/Widgets/Form/String
+++ b/share/html/Widgets/Form/String
@@ -50,7 +50,11 @@ see docs/extending/using_forms_widgets.pod
</%DOC>
<div id="form-box-<% lc $Name %>" class="widget form-row">
<div class="col-md-3 label">
+% if( $LabelLink ) {
+ <a href="<% $LabelLink %>"><% $Description %></a>
+% } else {
<% $Description // '' %>
+% }
</div>
<div class="col-md-9 value">
<& SELF:InputOnly, %ARGS &>
@@ -71,6 +75,7 @@ $CurrentValue => '',
$Default => 0,
$DefaultValue => '',
$DefaultLabel => loc( 'Default: [_1]', $DefaultValue ),
+$LabelLink => '',
</%ARGS>
<%METHOD InputOnly>
commit 1eee9b8f5580699d6ea70bf14017f0bbc1b25c74
Author: sunnavy <sunnavy at bestpractical.com>
Date: Mon Nov 18 22:13:07 2019 +0800
Vertically align boolean label/value
diff --git a/share/html/Widgets/Form/Boolean b/share/html/Widgets/Form/Boolean
index c965537d29..f54b6bcf16 100644
--- a/share/html/Widgets/Form/Boolean
+++ b/share/html/Widgets/Form/Boolean
@@ -48,7 +48,7 @@
<%DOC>
see docs/extending/using_forms_widgets.pod
</%DOC>
-<div id="form-box-<% lc $Name %>" class="widget form-row">
+<div id="form-box-<% lc $Name %>" class="widget form-row boolean">
<div class="col-md-3 label">
% if( $LabelLink ) {
<a href="<% $LabelLink %>"><% $Description %></a>
diff --git a/share/static/css/elevator-light/forms.css b/share/static/css/elevator-light/forms.css
index 2554a2afa9..e6c8943ff2 100644
--- a/share/static/css/elevator-light/forms.css
+++ b/share/static/css/elevator-light/forms.css
@@ -303,6 +303,10 @@ button {
margin-top: 0;
}
+.form-row.boolean > .value .form-row {
+ margin-top: 5px;
+}
+
.col-auto .datepicker {
width: 17em;
}
commit fb5f04660ac015cb761ade82b438a4110c6ca322
Author: michel <michel at bestpractical.com>
Date: Thu Nov 7 17:11:47 2019 +0100
Add tabs to the Configuration in DB feature
The tab/section structure is set in Config.pm, subsections
are defined by parsing RT_Config.pm.
The whole page now uses bootstrap with 4.6 styles.
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 08eee2a89b..2f961041ef 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -166,6 +166,7 @@ our %META;
Widget => '/Widgets/Form/Select',
WidgetArguments => {
Description => 'Default queue', #loc
+ Default => 1, # allow user to unset it on EditConfig.html
Callback => sub {
my $ret = { Values => [], ValuesLabel => {}};
my $q = RT::Queues->new($HTML::Mason::Commands::session{'CurrentUser'});
@@ -1886,6 +1887,133 @@ sub PostLoadCheck {
}
}
+=head2 SectionMap
+
+A data structure used to breakup the option list into tabs/sections/subsections/options
+This is done by parsing RT_Config.pm and extracting the section names and level
+
+=cut
+
+# initial data, manually created to give the tab structure and the order of the sections
+# the result of the parsing of RT_Config.pm will be added to this
+# sections will have content: a list of subsections with a Name and a Content
+our $SectionMap= [
+ { Name => 'System', # loc
+ Content => [
+ { Name => 'Base configuration' }, # loc
+ { Name => 'Database connection' }, # loc
+ { Name => 'Logging' }, # loc
+ { Name => 'Incoming mail gateway' }, # loc
+ { Name => 'Outgoing mail' }, # loc
+ { Name => 'Application logic' }, # loc
+ { Name => 'Extra security' }, # loc
+ { Name => 'Internationalization' }, # loc
+ { Name => 'Date and time handling' }, # loc
+ { Name => 'Initialdata Formats' }, # loc
+ { Name => 'Development options' }, # loc
+ ],
+ },
+ { Name => 'Web UI', # loc
+ Content => [
+ { Name => 'Web interface' }, # loc
+ ],
+ },
+ { Name => 'Features', # loc
+ Content => [
+ { Name => 'Assets' }, # loc
+ { Name => 'Cryptography' }, # loc
+ { Name => 'External storage' }, # loc
+ { Name => 'SLA' }, # loc
+ { Name => 'Administrative interface' }, # loc
+ ],
+ },
+ { Name => 'User Auth', # loc
+ Content => [
+ { Name => 'Authorization and user configuration' }, # loc
+ ],
+ },
+];
+
+our $SectionMapLoaded = 0; # so we only load it once
+
+sub LoadSectionMap {
+ my $self = shift;
+
+ if ($SectionMapLoaded) {
+ return $SectionMap;
+ }
+
+ # create a hash <section> => <tab> / Content so we know in which tab to look for a section
+ my %SectionIndex;
+ foreach my $Tab (@$SectionMap) {
+ my $TabName = $Tab->{Name};
+ foreach my $section ( @{ $Tab->{Content} } ) {
+ $section->{Content} = [];
+ $SectionIndex{ $section->{Name} } = { Tab => $TabName, Content => $section->{Content} };
+ }
+ }
+
+ my $ConfigFile = "$RT::EtcPath/RT_Config.pm";
+ require Pod::Simple::HTML;
+ my $PodParser = Pod::Simple::HTML->new();
+
+ my $html;
+ $PodParser->output_string( \$html );
+ $PodParser->parse_file($ConfigFile);
+
+ my $CurrentTabName;
+ my $CurrentSectionName;
+ my $CurrentSubSectionName;
+ my $CurrentSectionContent;
+
+ while ( $html =~ m{<(h[12]|dt)\b[^>]*>(.*?)</\1>}sg ) {
+ my ( $tag, $content ) = ( $1, $2 );
+ if ( $tag eq 'h1' ) {
+ my ( $id, $title ) = $content =~ m{<a class='u'\s*name="([^"]*)"\s*>([^<]*)</a>};
+ next if $title eq 'NAME';
+ $CurrentSectionName = $title;
+ if ( $SectionIndex{$CurrentSectionName}->{Tab} ) {
+ $CurrentTabName = $SectionIndex{$CurrentSectionName}->{Tab};
+
+ # create a sub section with no name, for section level options
+ push @{ $SectionIndex{$CurrentSectionName}->{Content} }, { Name => '', Content => [] };
+ $CurrentSectionContent = $SectionIndex{$CurrentSectionName}->{Content};
+ $CurrentSubSectionName = '';
+ }
+ else {
+ RT->Logger->debug("section $CurrentSectionName not found in SectionMap");
+ }
+ }
+ elsif ( $tag eq 'h2' ) {
+ my ( $id, $title ) = $content =~ m{<a class='u'\s*name="([^"]*)"\s*>([^<]*)</a>};
+ $CurrentSubSectionName = $title;
+ push @$CurrentSectionContent, { Name => $CurrentSubSectionName, Content => [] };
+ }
+ else {
+ # tag is 'dt'
+ my @options;
+
+ # a single item (dt) can document several options, in separate <code> elements
+ my ($name) = $content =~ m{name=".([^"]*)"};
+ $name =~ s{,_.}{-}g;
+ while ( $content =~ m{<code>(.)([^<]*)</code>}sg ) {
+ my ( $sigil, $option ) = ( $1, $2 );
+ next unless $sigil =~ m{[\@\%\$]}; # no sigil => this is a value for a select option
+ if ( $META{$option} ) {
+ my $LastSubSectionContent = $CurrentSectionContent->[-1]->{Content};
+ push @$LastSubSectionContent, { Name => $option, Help => $name };
+ }
+ else {
+ my $TabName = $SectionIndex{$CurrentSectionName}->{Name};
+ RT->Logger->debug("missing META info for option [$option]");
+ }
+ }
+ }
+ }
+ $SectionMapLoaded = 1;
+ return $SectionMap;
+}
+
=head2 Configs
Returns list of config files found in local etc, plugins' etc
@@ -2333,6 +2461,7 @@ sub RefreshConfigFromDatabase {
$in_config_change_txn = 0;
}
+ if( RT->InstallMode ) { return; } # RT can't load the config in the DB if the DB is not there!
my $needs_update = RT->System->ConfigCacheNeedsUpdate;
if ($needs_update > $database_config_cache_time) {
$self->LoadConfigFromDatabase();
diff --git a/share/html/Admin/Tools/Config/Elements/Option b/share/html/Admin/Tools/Config/Elements/Option
new file mode 100644
index 0000000000..ff216fd323
--- /dev/null
+++ b/share/html/Admin/Tools/Config/Elements/Option
@@ -0,0 +1,155 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+
+<%PERL>
+
+use Data::Dumper;
+my $stringify = sub {
+ my $value = shift;
+ return "" if !defined($value);
+
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Indent = 2;
+ local $Data::Dumper::Sortkeys = 1;
+ my $output = Dumper $value;
+ chomp $output;
+ return $output;
+};
+
+my $doc_version = $RT::VERSION;
+$doc_version =~ s/\.\d+-\d+-g\w+$//; # 4.4.3-1-g123 -> 4.4
+
+my $name = $option->{Name};
+my $meta = RT->Config->Meta( $name );
+return if $meta->{Invisible} || $meta->{Deprecated};
+
+my $has_execute_code = $session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System);
+
+my $raw_value = RT->Config->Get( $name );
+my $val = $stringify->($raw_value);
+my $doc_url = "https://docs.bestpractical.com/rt/$doc_version/RT_Config.html#$option->{Help}";
+my $widget = $meta->{'Widget'} || '/Widgets/Form/Code';
+my $is_code = $widget eq '/Widgets/Form/Code';
+my $is_password = ($name =~ /Password/i and $name !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ );
+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'} || {};
+if ($widget eq '/Widgets/Form/Boolean') {
+ %$args = (
+ RadioStyle => 1,
+ %$args,
+ );
+}
+elsif ($widget eq '/Widgets/Form/String' || $widget eq '/Widgets/Form/Integer') {
+ %$args = (
+ Size => 60,
+ %$args,
+ );
+}
+elsif ($widget eq '/Widgets/Form/Select') {
+ %$args = (
+ $args->{Default} ? ( DefaultLabel => loc('(no value)') ) : (),
+ %$args,
+ );
+}
+my $row_start = qq{<div class="widget form-row">
+ <div class="col-md-3 label"><a href="$doc_url" target="_blank">$name</a></div>
+ <div class="col-md-9 value">
+};
+my $row_end = qq{</div></div>};
+
+</%PERL>
+
+<!-- start option <% $name %> -->
+% if ( $meta->{EditLink} ) {
+% if ($widget eq '/Widgets/Form/MultilineString' || $widget eq '/Widgets/Form/Code') {
+<% $row_start |n %><textarea disabled class="<% $is_code ? 'code' : '' %> form-control" rows="6" cols="80"><% $current_value %></textarea><br />
+% } else {
+<% $row_start |n %><input type="text" disabled width="80" value="<% $current_value %>" class="form-control" /><br/>
+% }
+<&|/l_unsafe, "<a href=\"$meta->{EditLink}\">", loc($meta->{EditLinkLabel}), "</a>" &>Visit [_1][_2][_3] to manage this setting</&>
+% } elsif ( $name =~ /Plugins/) {
+<% $row_start |n %><ul class="plugins">
+% for my $plugin (RT->Config->Get($name)) {
+<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>
+<% $row_end |n%>
+% } elsif ( $is_password ) {
+<em><% loc('Must modify in config file' ) %></em><br />
+% } elsif ( $is_immutable ) {
+% if ($widget eq '/Widgets/Form/MultilineString' || $widget eq '/Widgets/Form/Code') {
+<% $row_start |n %><textarea disabled class="<% $is_code ? 'code' : '' %> form-control" rows="6" cols="80"><% $current_value %></textarea>
+% } else {
+<% $row_start |n %><input type="text" disabled width="80" value="<% $current_value %>" class="form-control" />
+% }
+<br /><em><% loc('Must modify in config file' ) %></em>
+<% $row_end |n %>
+% } else {
+ <& $widget,
+ Default => 0,
+ Name => $name,
+ LabelLink => $doc_url,
+ CurrentValue => $current_value,
+ Description => $name,
+ Hints => $meta->{WidgetArguments}->{Hints} || '',
+ %$args,
+ %{ $m->comp('/Widgets/FinalizeWidgetArguments', WidgetArguments =>
+ $meta->{'WidgetArguments'} ) },
+ &>
+<textarea class="hidden" name="<% $name %>-Current"><% $current_value %></textarea>
+% }
+<!-- end option <% $name %> -->
+<%ARGS>
+$option
+</%ARGS>
diff --git a/share/html/Admin/Tools/Config/Elements/Section b/share/html/Admin/Tools/Config/Elements/Section
new file mode 100644
index 0000000000..f1454f8ed5
--- /dev/null
+++ b/share/html/Admin/Tools/Config/Elements/Section
@@ -0,0 +1,60 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+
+% my $section_id = CSSClass( $section->{Name} );
+
+% foreach my $subsection ( @{$section->{Content}} ) {
+% $current_context->{subsection} = CSSClass( $subsection->{Name});
+ <& /Admin/Tools/Config/Elements/SubSection, subsection => $subsection, active_context => $active_context, current_context => $current_context &>
+ <!-- end subsection <% $subsection->{Name} %> -->
+% }
+<%ARGS>
+$section
+$active_context
+$current_context
+</%ARGS>
diff --git a/share/html/Admin/Tools/Config/Elements/SubSection b/share/html/Admin/Tools/Config/Elements/SubSection
new file mode 100644
index 0000000000..5e407ea9c5
--- /dev/null
+++ b/share/html/Admin/Tools/Config/Elements/SubSection
@@ -0,0 +1,77 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+
+% if( @{$subsection->{Content}}) {
+% my $id = join '-', 'form', $current_context->{tab}, $current_context->{section}, $current_context->{subsection} || ();
+<form id="<% $id %>" name="EditConfig" method="post" action="EditConfig.html#<% $id %>">
+ <input type="hidden" name="Update" value="1" />
+% my $complete_title =
+% join ' - ', map { s{_}{ }g; loc( $_ ) }
+% grep { $_ }
+% map { $current_context->{$_} }
+% (qw( tab section subsection ) );
+<&|/Widgets/TitleBox, title => $complete_title &>
+% foreach my $option ( @{$subsection->{Content}} ) {
+ <& /Admin/Tools/Config/Elements/Option, option => $option &>
+% }
+<input type="hidden" name="tab" value="<% $current_context->{tab} %>" />
+<input type="hidden" name="section" value="<% $current_context->{section} %>" />
+<input type="hidden" name="subsection" value="<% $current_context->{subsection} %>" />
+<div class="form-row">
+ <span class="col-md-12">
+ <& /Elements/Submit, Label => loc('Save Changes') &>
+ </span>
+</div>
+</&>
+</form>
+% }
+<%ARGS>
+$subsection
+$active_context
+$current_context
+</%ARGS>
diff --git a/share/html/Admin/Tools/Config/Elements/Tab b/share/html/Admin/Tools/Config/Elements/Tab
new file mode 100644
index 0000000000..39e2ecf0c8
--- /dev/null
+++ b/share/html/Admin/Tools/Config/Elements/Tab
@@ -0,0 +1,95 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+
+% my $nav_type = 'pill'; # 'tab' or 'pill'
+% my $tab_id = CSSClass( $tab->{Name} );
+<div class="row">
+ <div class="col-3">
+% my @section_names = map { $_->{Name} } @{$tab->{Content}};
+ <ul class="nav nav-<% $nav_type %>s flex-column navbar-fixed-top" id="config-sections-<% $tab_id %>" aria-orientation="vertical">
+% my $first_section = 1;
+% foreach my $section_name (@section_names) {
+% $current_context->{section} = CSSClass( $section_name );
+% my $active = $current_context->{tab} eq $active_context->{tab} ?
+% $current_context->{section} eq $active_context->{section} :
+% $first_section;
+% $first_section = 0;
+% my( $active_class, $aria_selected) = $active ? ('active', 'true') : ('', 'false');
+% my $nav_id = join '-', 'nav', $current_context->{tab}, $current_context->{section};
+% my $content_id = join '-', 'content', $current_context->{tab}, $current_context->{section};
+ <li class="nav-item">
+ <a class="nav-link <% $active_class %>" id="<% $nav_id %>" data-toggle="<% $nav_type %>" href="#<% $content_id %>" role="<% $nav_type %>" aria-controls="<% $nav_id %>" aria-selected="<% $aria_selected %>"><% $section_name %></a>
+ </li>
+% }
+ </ul>
+ </div>
+ <div class="col-9">
+ <div class="tab-content" id="tab-content-<% $tab_id %>" >
+
+% $first_section = 1;
+% foreach my $section ( @{$tab->{Content}} ) {
+% $current_context->{section} = CSSClass( $section->{Name} );
+% my $active = $current_context->{tab} eq $active_context->{tab} ?
+% $current_context->{section} eq $active_context->{section} :
+% $first_section;
+% my $active_class = $active ? 'active show' : '';
+% $first_section = 0;
+% my $nav_id = join '-', 'nav', $current_context->{tab}, $current_context->{section};
+% my $content_id = join '-', 'content', $current_context->{tab}, $current_context->{section};
+ <div class="tab-pane fade <% $active_class %>" role="tabpanel" id="<% $content_id %>" aria-labelledby="<% $nav_id %>">
+ <& /Admin/Tools/Config/Elements/Section, section => $section, current_context => $current_context, active_context => $active_context &>
+ </div><!-- end section <% $content_id %> -->
+% }
+ </div><!-- end of tab tab-content-<% $tab_id %> -->
+ </div>
+</div>
+<%ARGS>
+$tab
+$active_context
+$current_context
+</%ARGS>
diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
index 62adc78734..677e70c359 100644
--- a/share/html/Admin/Tools/EditConfig.html
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -53,11 +53,14 @@ unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'Super
my $has_execute_code = $session{CurrentUser}->HasRight(Right => 'ExecuteCode', Object => RT->System);
-my @results;
+my $options = RT->Config->LoadSectionMap();
+my $active_context = {
+ tab => CSSClass( $ARGS{tab} || $options->[0]->{Name}) ,
+ section => CSSClass( $ARGS{section} || $options->[0]->{Content}->[0]->{Name}) ,
+ subsection => CSSClass( $ARGS{subsection} || $options->[0]->{Content}->[0]->{Content}->[0]->{Name}) ,
+};
-my $doc_version = $RT::VERSION;
-$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
+my @results;
use Data::Dumper;
my $stringify = sub {
@@ -80,6 +83,7 @@ if (delete $ARGS{Update}) {
eval {
for my $key (keys %ARGS) {
next if $key =~ /-Current$/;
+ next if $key eq 'tab' || $key eq 'section' || $key eq 'subsection';
my $meta = RT->Config->Meta( $key );
my $widget = $meta->{Widget} || '/Widgets/Form/Code';
@@ -137,7 +141,11 @@ if (delete $ARGS{Update}) {
$setting->Load($key);
if ($setting->Id) {
if ($setting->Disabled) {
- $setting->SetDisabled(0);
+ my ($ok, $msg) = $setting->SetDisabled(0);
+ if (!$ok) {
+ push @results, $msg;
+ $has_error++;
+ }
}
my ($ok, $msg) = $setting->SetContent($val);
@@ -170,106 +178,38 @@ if (delete $ARGS{Update}) {
RT->Config->EndDatabaseConfigChanges;
}
+my $nav_type='tab'; # 'tab' or 'pill'
+
</%INIT>
<& /Admin/Elements/Header, Title => $title &>
<& /Elements/Tabs &>
<& /Elements/ListActions, actions => \@results &>
-<form id="EditConfig" method="post" action="EditConfig.html">
-<input type="hidden" name="Update" value=1></input>
-
-<&|/Widgets/TitleBox, title => loc("RT Configuration") &>
-<table border="0" cellspacing="0" cellpadding="5" width="100%" class="collection">
-<tr class="collection-as-table">
-<th class="collection-as-table"><&|/l&>Option</&></th>
-<th class="collection-as-table"><&|/l&>Value</&></th>
-</tr>
-<%PERL>
-my $index_conf;
-foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
- my $meta = RT->Config->Meta( $key );
-
- next if $meta->{Invisible} || $meta->{Deprecated};
-
- my $raw_value = RT->Config->Get( $key );
- my $val = $stringify->($raw_value);
-
- $index_conf++;
-
- 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 $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'} || {};
-
- if ($widget eq '/Widgets/Form/Boolean') {
- %$args = (
- Default => 0,
- RadioStyle => 1,
- %$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>
-<td class="collection-as-table">
-
-% if ( $meta->{EditLink} ) {
-% if ($widget eq '/Widgets/Form/MultilineString' || $widget eq '/Widgets/Form/Code') {
-<textarea disabled class="<% $is_code ? 'code' : '' %>" rows="6" cols="80"><% $current_value %></textarea><br>
-% } else {
-<input type="text" disabled width="80" value="<% $current_value %>"></input><br>
+<div class="titlebox-content">
+% my @tab_names = map { $_->{Name} } @$options;
+ <ul class="nav nav-<% $nav_type %>s" id="config-tabs">
+% my $current_context = {};
+% foreach my $tab_name (@tab_names) {
+% my $tab_id = CSSClass( $tab_name );
+% $current_context->{tab} = $tab_id;
+% my( $active, $aria_selected) = $tab_id eq $active_context->{tab} ? ('active', 'true') : ('', 'false');
+% my $nav_id = join '-', 'nav', $current_context->{tab};
+% my $content_id = join '-', 'content', $current_context->{tab};
+ <li class="nav-item">
+ <a class="nav-link <% $active %>" id="<% $nav_id %>" data-toggle="<% $nav_type %>" href="#<% $content_id %>" role="<% $nav_type %>" aria-controls="<% $content_id %>" aria-selected="<% $aria_selected %>"><% $tab_name %></a>
+ </li>
% }
-<&|/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>
+ <div class="tab-content" id="content-all" >
+% foreach my $tab ( @$options) {
+% my $tab_id = CSSClass( $tab->{Name} );
+% $current_context->{tab} = $tab_id;
+% my $active = $tab_id eq $active_context->{tab} ? ' show active' : '';
+% my $nav_id = join '-', 'nav', $current_context->{tab};
+% my $content_id = join '-', 'content', $current_context->{tab};
+ <div class="tab-pane fade<% $active %>" role="tabpanel" id="<% $content_id %>" aria-labelledby="<% $nav_id %>">
+ <& /Admin/Tools/Config/Elements/Tab, tab => $tab, active_context => $active_context, current_context => $current_context &>
+ </div><!-- <% $content_id %> -->
% }
-</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 {
-<input type="text" disabled width="80" value="<% $current_value %>"></input>
-% }
-<br><em><% loc('Must modify in config file' ) %></em>
-% } else {
- <& $widget,
- Default => 1,
- DefaultValue => '',
- DefaultLabel => '(no value)',
-
- %{ $m->comp('/Widgets/FinalizeWidgetArguments', WidgetArguments => $args ) },
- Name => $key,
- CurrentValue => $current_value,
- Description => '',
- Hints => '',
- &>
-<textarea class="hidden" name="<% $key %>-Current"><% $current_value %></textarea>
-% }
-</td>
-</tr>
-% }
-</table>
-</&>
-<& /Elements/Submit, Label => loc('Save Changes') &>
-</form>
-
+ </div><!-- content-all -->
+</div><!-- titlebox-content -->
commit 21ff2fa0c2e3fc8853a70d1eff9812597c19df55
Author: michel <michel at bestpractical.com>
Date: Thu Nov 7 17:17:14 2019 +0100
Add missing config options to %META
Previously config options did not have to be added to %META, config
in UI now requires it to generate the input form.
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 2f961041ef..50ecd6f601 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -609,6 +609,21 @@ our %META;
}
},
},
+ CanonicalizeEmailAddressMatch => {
+ Section => 'Mail', #loc
+ Type => 'SCALAR',
+ Widget => '/Widgets/Form/String',
+ },
+ CanonicalizeEmailAddressReplace => {
+ Section => 'Mail', #loc
+ Type => 'SCALAR',
+ Widget => '/Widgets/Form/String',
+ },
+ EmailSubjectTagRegex => {
+ Section => 'Mail', #loc
+ Type => 'SCALAR',
+ Widget => '/Widgets/Form/String',
+ },
# User overridable mail options
EmailFrequency => {
Section => 'Mail', #loc
@@ -1323,6 +1338,12 @@ our %META;
}
},
},
+ UserAutocreateDefaultsOnLogin => {
+ Type => 'HASH',
+ },
+ AutoCreateNonExternalUsers => {
+ Widget => '/Widgets/Form/Boolean',
+ },
ServiceAgreements => {
Type => 'HASH',
},
@@ -1629,7 +1650,18 @@ our %META;
WebImagesURL => {
Widget => '/Widgets/Form/String',
},
-
+ AssetQueues => {
+ Type => 'ARRAY',
+ Hints => '',
+ },
+ AssetBasicCustomFieldsOnCreate => {
+ Type => 'ARRAY',
+ Hints => '[ "foo", "bar"]',
+ },
+ DefaultCatalog => {
+ Widget => '/Widgets/Form/String',
+ Hints => 'General assets',
+ },
AssetSearchFormat => {
Widget => '/Widgets/Form/MultilineString',
},
commit 5523105073afa2c99ece9b90c03fc591ad21cfcb
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue Nov 19 08:33:42 2019 +0800
Rename DatabaseSetting to Configuration
The meaning of "DatabaseSetting" is a bit confusing.
diff --git a/etc/acl.Pg b/etc/acl.Pg
index a48373ec6d..41a44b16c1 100644
--- a/etc/acl.Pg
+++ b/etc/acl.Pg
@@ -66,8 +66,8 @@ sub acl {
CustomRoles
objectcustomroles_id_seq
ObjectCustomRoles
- databasesettings_id_seq
- DatabaseSettings
+ configurations_id_seq
+ Configurations
);
my $db_user = RT->Config->Get('DatabaseUser');
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index b4ce7404e4..b71b41d9d4 100644
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -539,10 +539,10 @@ CREATE TABLE ObjectCustomRoles (
);
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
-CREATE SEQUENCE DatabaseSettings_seq;
-CREATE TABLE DatabaseSettings (
- id NUMBER(11,0) CONSTRAINT DatabaseSettings_key PRIMARY KEY,
- Name VARCHAR2(255) CONSTRAINT DatabaseSettings_Name_Unique unique NOT NULL,
+CREATE SEQUENCE Configurations_seq;
+CREATE TABLE Configurations (
+ id NUMBER(11,0) CONSTRAINT Configurations_key PRIMARY KEY,
+ Name VARCHAR2(255) CONSTRAINT Configurations_Name_Unique unique NOT NULL,
Content CLOB,
ContentType VARCHAR2(80),
Disabled NUMBER(11,0) DEFAULT 0 NOT NULL,
@@ -552,6 +552,6 @@ CREATE TABLE DatabaseSettings (
LastUpdated DATE
);
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (LOWER(Name));
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 8c44a8f853..eda93108df 100644
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -779,9 +779,9 @@ CREATE TABLE ObjectCustomRoles (
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
-CREATE SEQUENCE databasesettings_id_seq;
-CREATE TABLE DatabaseSettings (
- id integer DEFAULT nextval('databasesettings_id_seq'),
+CREATE SEQUENCE configurations_id_seq;
+CREATE TABLE Configurations (
+ id integer DEFAULT nextval('configurations_id_seq'),
Name varchar(255) NOT NULL,
Content text NULL,
ContentType varchar(80) NULL,
@@ -793,6 +793,6 @@ CREATE TABLE DatabaseSettings (
PRIMARY KEY (id)
);
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (LOWER(Name));
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 64d395a866..b6e0166345 100644
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -570,7 +570,7 @@ CREATE TABLE ObjectCustomRoles (
);
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
-CREATE TABLE DatabaseSettings (
+CREATE TABLE Configurations (
id INTEGER PRIMARY KEY,
Name varchar(255) collate NOCASE NOT NULL,
Content longtext collate NOCASE NULL,
@@ -582,6 +582,6 @@ CREATE TABLE DatabaseSettings (
LastUpdated timestamp DEFAULT NULL
);
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (Name);
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/etc/schema.mysql b/etc/schema.mysql
index f37469e89a..1d6da82c59 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -560,7 +560,7 @@ CREATE TABLE ObjectCustomRoles (
CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
-CREATE TABLE DatabaseSettings (
+CREATE TABLE Configurations (
id int(11) NOT NULL AUTO_INCREMENT,
Name varchar(255) NOT NULL,
Content longblob NULL,
@@ -573,6 +573,6 @@ CREATE TABLE DatabaseSettings (
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (Name);
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/etc/upgrade/4.5.0/acl.Pg b/etc/upgrade/4.5.0/acl.Pg
index 6a73f0cd45..05e01ab640 100644
--- a/etc/upgrade/4.5.0/acl.Pg
+++ b/etc/upgrade/4.5.0/acl.Pg
@@ -3,8 +3,8 @@ sub acl {
my @acls;
my @tables = qw (
- databasesettings_id_seq
- DatabaseSettings
+ configurations_id_seq
+ Configurations
);
my $db_user = RT->Config->Get('DatabaseUser');
diff --git a/etc/upgrade/4.5.0/schema.Oracle b/etc/upgrade/4.5.0/schema.Oracle
index 17fed3dc1c..81df05cd0f 100644
--- a/etc/upgrade/4.5.0/schema.Oracle
+++ b/etc/upgrade/4.5.0/schema.Oracle
@@ -1,8 +1,8 @@
ALTER TABLE Classes DROP( HotList );
-CREATE SEQUENCE DatabaseSettings_seq;
-CREATE TABLE DatabaseSettings (
- id NUMBER(11,0) CONSTRAINT DatabaseSettings_key PRIMARY KEY,
- Name VARCHAR2(255) CONSTRAINT DatabaseSettings_Name_Unique unique NOT NULL,
+CREATE SEQUENCE Configurations_seq;
+CREATE TABLE Configurations (
+ id NUMBER(11,0) CONSTRAINT Configurations_key PRIMARY KEY,
+ Name VARCHAR2(255) CONSTRAINT Configurations_Name_Unique unique NOT NULL,
Content CLOB,
ContentType VARCHAR2(80),
Disabled NUMBER(11,0) DEFAULT 0 NOT NULL,
@@ -12,5 +12,5 @@ CREATE TABLE DatabaseSettings (
LastUpdated DATE
);
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (LOWER(Name));
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/etc/upgrade/4.5.0/schema.Pg b/etc/upgrade/4.5.0/schema.Pg
index 0efa1411d0..fca1a417c8 100644
--- a/etc/upgrade/4.5.0/schema.Pg
+++ b/etc/upgrade/4.5.0/schema.Pg
@@ -1,7 +1,7 @@
ALTER TABLE Classes DROP COLUMN HotList;
-CREATE SEQUENCE databasesettings_id_seq;
-CREATE TABLE DatabaseSettings (
- id integer DEFAULT nextval('databasesettings_id_seq'),
+CREATE SEQUENCE configurations_id_seq;
+CREATE TABLE Configurations (
+ id integer DEFAULT nextval('configurations_id_seq'),
Name varchar(255) NOT NULL,
Content text NULL,
ContentType varchar(80) NULL,
@@ -13,5 +13,5 @@ CREATE TABLE DatabaseSettings (
PRIMARY KEY (id)
);
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (LOWER(Name));
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/etc/upgrade/4.5.0/schema.SQLite b/etc/upgrade/4.5.0/schema.SQLite
index a25a8c9b49..a8f280d334 100644
--- a/etc/upgrade/4.5.0/schema.SQLite
+++ b/etc/upgrade/4.5.0/schema.SQLite
@@ -1,4 +1,4 @@
-CREATE TABLE DatabaseSettings (
+CREATE TABLE Configurations (
id INTEGER PRIMARY KEY,
Name varchar(255) collate NOCASE NOT NULL,
Content longtext collate NOCASE NULL,
@@ -10,6 +10,6 @@ CREATE TABLE DatabaseSettings (
LastUpdated timestamp DEFAULT NULL
);
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (Name);
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/etc/upgrade/4.5.0/schema.mysql b/etc/upgrade/4.5.0/schema.mysql
index 444678cfc4..60a55b54cc 100644
--- a/etc/upgrade/4.5.0/schema.mysql
+++ b/etc/upgrade/4.5.0/schema.mysql
@@ -1,5 +1,5 @@
ALTER TABLE Classes DROP COLUMN HotList;
-CREATE TABLE DatabaseSettings (
+CREATE TABLE Configurations (
id int(11) NOT NULL AUTO_INCREMENT,
Name varchar(255) NOT NULL,
Content longblob NULL,
@@ -12,5 +12,5 @@ CREATE TABLE DatabaseSettings (
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
-CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
+CREATE UNIQUE INDEX Configurations1 ON Configurations (Name);
+CREATE INDEX Configurations2 ON Configurations (Disabled);
diff --git a/lib/RT.pm b/lib/RT.pm
index c53cbb5e38..63169ffdc4 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -504,8 +504,8 @@ sub InitClasses {
require RT::Asset;
require RT::Assets;
require RT::CustomFieldValues::Canonicalizer;
- require RT::DatabaseSetting;
- require RT::DatabaseSettings;
+ require RT::Configuration;
+ require RT::Configurations;
_BuildTableAttributes();
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 50ecd6f601..405b31dbcf 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -2506,7 +2506,7 @@ sub LoadConfigFromDatabase {
$database_config_cache_time = time;
- my $settings = RT::DatabaseSettings->new(RT->SystemUser);
+ my $settings = RT::Configurations->new(RT->SystemUser);
$settings->UnLimit;
my %seen;
diff --git a/lib/RT/DatabaseSetting.pm b/lib/RT/Configuration.pm
similarity index 98%
rename from lib/RT/DatabaseSetting.pm
rename to lib/RT/Configuration.pm
index 185aae01f8..82886798b2 100644
--- a/lib/RT/DatabaseSetting.pm
+++ b/lib/RT/Configuration.pm
@@ -50,7 +50,7 @@ use strict;
use warnings;
use 5.10.1;
-package RT::DatabaseSetting;
+package RT::Configuration;
use base 'RT::Record';
use Storable ();
@@ -59,7 +59,7 @@ use JSON ();
=head1 NAME
-RT::DatabaseSetting - Represents a config setting
+RT::Configuration - Represents a config setting
=cut
@@ -209,7 +209,7 @@ sub ValidateName {
return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
- my $TempSetting = RT::DatabaseSetting->new( RT->SystemUser );
+ my $TempSetting = RT::Configuration->new( RT->SystemUser );
$TempSetting->Load($name);
if ( $TempSetting->id && ( !$self->id || $TempSetting->id != $self->id ) ) {
@@ -320,7 +320,7 @@ sub SetContent {
=head1 PRIVATE METHODS
Documented for internal use only, do not call these from outside
-RT::DatabaseSetting itself.
+RT::Configuration itself.
=head2 _Set
@@ -397,7 +397,7 @@ sub _DeJSONContent {
return $thawed;
}
-sub Table { "DatabaseSettings" }
+sub Table { "Configurations" }
sub _CoreAccessible {
{
diff --git a/lib/RT/DatabaseSettings.pm b/lib/RT/Configurations.pm
similarity index 92%
rename from lib/RT/DatabaseSettings.pm
rename to lib/RT/Configurations.pm
index 3236bad2c0..36ff2622d7 100644
--- a/lib/RT/DatabaseSettings.pm
+++ b/lib/RT/Configurations.pm
@@ -49,18 +49,18 @@
use strict;
use warnings;
-package RT::DatabaseSettings;
+package RT::Configurations;
use base 'RT::SearchBuilder';
=head1 NAME
-RT::DatabaseSettings - a collection of L<RT::DatabaseSettings> objects
+RT::Configurations - a collection of L<RT::Configurations> objects
=cut
sub NewItem {
my $self = shift;
- return RT::DatabaseSetting->new( $self->CurrentUser );
+ return RT::Configuration->new( $self->CurrentUser );
}
=head2 _Init
@@ -78,7 +78,7 @@ sub _Init {
return $self->SUPER::_Init( @_ );
}
-sub Table { "DatabaseSettings" }
+sub Table { "Configurations" }
1;
diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
index 677e70c359..e9d3d63536 100644
--- a/share/html/Admin/Tools/EditConfig.html
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -137,7 +137,7 @@ if (delete $ARGS{Update}) {
}
}
- my $setting = RT::DatabaseSetting->new($session{CurrentUser});
+ my $setting = RT::Configuration->new($session{CurrentUser});
$setting->Load($key);
if ($setting->Id) {
if ($setting->Disabled) {
commit d80765bfaf635b7a67d34952845c7804c45b4d3a
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue Nov 19 09:08:10 2019 +0800
Validate Content of Configurations
Before saving to database, we need to make sure the config type is
correct, otherwise server could die with errors like:
Can't use string ("foo") as an ARRAY ref
diff --git a/lib/RT/Configuration.pm b/lib/RT/Configuration.pm
index 82886798b2..dcb96ca15d 100644
--- a/lib/RT/Configuration.pm
+++ b/lib/RT/Configuration.pm
@@ -116,6 +116,9 @@ sub Create {
return ( 0, $self->loc("You cannot update [_1] using database config; you must edit your site config", $args{'Name'}) );
}
+ ( $id, $msg ) = $self->ValidateContent( Name => $args{'Name'}, Content => $args{'Content'} );
+ return ( 0, $msg ) unless $id;
+
if (ref ($args{'Content'}) ) {
($args{'Content'}, my $error) = $self->_SerializeContent($args{'Content'}, $args{'Name'});
if ($error) {
@@ -272,6 +275,9 @@ sub SetContent {
return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
+ my ( $ok, $msg ) = $self->ValidateContent( Content => $value );
+ return (0, $msg) unless $ok;
+
my ($old_value, $error) = $self->Content;
unless (defined($old_value) && length($old_value)) {
$old_value = $self->loc('(no value)');
@@ -287,7 +293,7 @@ sub SetContent {
$RT::Handle->BeginTransaction;
- my ($ok, $msg) = $self->_Set( Field => 'Content', Value => $value );
+ ($ok, $msg) = $self->_Set( Field => 'Content', Value => $value );
if (!$ok) {
$RT::Handle->Rollback;
return ($ok, $self->loc("Unable to update [_1]: [_2]", $self->Name, $msg));
@@ -317,6 +323,35 @@ sub SetContent {
}
}
+=head2 ValidateContent
+
+Returns either (0, "failure reason") or 1 depending on whether the given
+content is valid.
+
+=cut
+
+sub ValidateContent {
+ my $self = shift;
+ my %args = @_ == 1 ? ( Content => @_ ) : @_;
+ $args{Name} ||= $self->Name;
+
+ # Validate methods are automatically called on Create by RT::Record.
+ # Sadly we have to skip that because it doesn't pass other field values,
+ # which we need here, as content type depends on the config name.
+ # We need to explicitly call Validate ourselves instead.
+ return 1 unless $args{Name};
+
+ my $meta = RT->Config->Meta( $args{Name} );
+ if ( my $type = $meta->{Type} ) {
+ if ( ( $type eq 'ARRAY' && ref $args{Content} ne 'ARRAY' )
+ || ( $type eq 'HASH' && ref $args{Content} ne 'HASH' ) )
+ {
+ return ( 0, $self->loc( 'Invalid value for [_1], should be of type [_2]', $args{Name}, $type ) );
+ }
+ }
+ return 1;
+}
+
=head1 PRIVATE METHODS
Documented for internal use only, do not call these from outside
commit 1ada6c8511c786fe678c0a84975bb07226d65ed3
Author: sunnavy <sunnavy at bestpractical.com>
Date: Wed Nov 20 04:33:56 2019 +0800
Use Data::Dumper instead in Configuration to support regex
And because of this change, we can more easily show detailed changes,
instead of vague messages like "Foo changed".
diff --git a/lib/RT/Configuration.pm b/lib/RT/Configuration.pm
index dcb96ca15d..2b005e6b5a 100644
--- a/lib/RT/Configuration.pm
+++ b/lib/RT/Configuration.pm
@@ -83,7 +83,7 @@ using L<Storable>. Otherwise any string is passed through as-is.
=item ContentType
-Currently handles C<storable> or C<application/json>.
+Currently handles C<perl> or C<application/json>.
=back
@@ -124,7 +124,7 @@ sub Create {
if ($error) {
return (0, $error);
}
- $args{'ContentType'} = 'storable';
+ $args{'ContentType'} = 'perl';
}
my $old_value = RT->Config->Get($args{Name});
@@ -147,14 +147,20 @@ sub Create {
$content = $self->loc('(no value)');
}
- 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));
+ if ( ref $old_value ) {
+ $old_value = $self->_SerializeContent($old_value);
}
+
+ RT->Logger->info(
+ sprintf(
+ '%s changed %s from "%s" to "%s"',
+ $self->CurrentUser->Name,
+ $self->Name,
+ $old_value // '',
+ $content // ''
+ )
+ );
+ return ( $id, $self->loc( '[_1] changed from "[_2]" to "[_3]"', $self->Name, $old_value // '', $content // '' ) );
}
=head2 CurrentUserCanSee
@@ -254,7 +260,7 @@ sub DecodedContent {
my $type = $self->__Value('ContentType') || '';
- if ($type eq 'storable') {
+ if ($type eq 'perl') {
return $self->_DeserializeContent($content);
}
elsif ($type eq 'application/json') {
@@ -288,7 +294,7 @@ sub SetContent {
if ($error) {
return (0, $error);
}
- $content_type = 'storable';
+ $content_type = 'perl';
}
$RT::Handle->BeginTransaction;
@@ -395,14 +401,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 (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));
- }
-
+ require Data::Dumper;
+ local $Data::Dumper::Terse = 1;
+ my $frozen = Data::Dumper::Dumper($content);
+ chomp $frozen;
return $frozen;
}
@@ -410,10 +412,10 @@ sub _DeserializeContent {
my $self = shift;
my $content = shift;
- my $thawed = eval { Storable::thaw(decode_base64($content)) };
+ my $thawed = eval "$content";
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));
+ $RT::Logger->error("Perl deserialization of database setting " . $self->Name . " failed: $error");
+ return (undef, $self->loc("Perl deserialization of database setting [_1] failed: [_2]", $self->Name, $error));
}
return $thawed;
commit 165ca1445fd4beb962b334471ac8dc34c91cde4b
Author: sunnavy <sunnavy at bestpractical.com>
Date: Wed Nov 20 04:52:56 2019 +0800
Note RT::Extension::ConfigInDatabase is cored and the main backend change
diff --git a/devel/docs/UPGRADING-4.6 b/devel/docs/UPGRADING-4.6
index 77a8d82480..2b25f206ed 100644
--- a/devel/docs/UPGRADING-4.6
+++ b/devel/docs/UPGRADING-4.6
@@ -20,6 +20,12 @@ page. If you previously used this callback to add to the bottom of the SelfServi
page, a new callback C<AfterMyGroupRequests> is now available below the new group
ticket listing.
+=item *
+
+When we cored RT::Extension::ConfigInDatabase, we renamed table name to
+Configurations and also changed internal implementation to support storing
+regex there.
+
=back
=cut
diff --git a/lib/RT.pm b/lib/RT.pm
index 63169ffdc4..d8116cf04d 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -766,6 +766,7 @@ our %CORED_PLUGINS = (
'RT::Extension::FutureMailgate' => '4.4',
'RT::Extension::AdminConditionsAndActions' => '4.4.2',
'RT::Extension::RightsInspector' => '4.6',
+ 'RT::Extension::ConfigInDatabase' => '4.6',
);
sub InitPlugins {
-----------------------------------------------------------------------
More information about the rt-commit
mailing list