%# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2023 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Admin/Elements/Header, Title => loc("Theme"), &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>

## Logo

<& /Elements/Logo, id => 'logo-theme-editor', ShowName => 0 &>
<&|/l&>Upload a new logo
% if ($valid_image_types) { <&|/l, $valid_image_types &>Your system supports automatic color suggestions for: [_1] % } else { <&|/l&>GD is disabled or not installed. You can upload an image, but you won't get automatic color suggestions. % }

## <&|/l&>Custom CSS (Advanced)

<% $user_css %>

## <&|/l&>Customize the RT theme

1. <&|/l&>Select a section:
2. <&|/l&>Select a color for the section:
   <&|/l&>You can also click on the logo above to get colors!
   % if ($colors) {
   % for (@$colors) { % my $fg = $_->{l} >= $text_threshold ? 'black' : 'white';
   % }
   % }

<%ONCE> my @sections = ( ['Page' => ['body', 'div#body']], ['Menu bar' => ['div#quickbar', '#main-navigation #app-nav.sf-shadow > li, #main-navigation #app-nav.sf-shadow > li > a, #prefs-menu > li, #prefs-menu > li > a, #logo .rtname']], ['Title bar' => ['div#header']], ['Page title' => ['div#header h1']], ['Page content' => ['div#body, .body-content-class']], ['Buttons' => ['input[type="reset"], input[type="submit"], input[class="button"]']], ['Button hover' => ['input[type="reset"]:hover, input[type="submit"]:hover, input[class="button"]:hover']], ['Search results' => ['table.collection-as-table th.collection-as-table a']], ['Portlet Text' => ['.titlebox .titlebox-title .left a, .titlebox .titlebox-title .left']], ['Selected bubble' => ['.selectize-control.multi .selectize-input [data-value]']], ['Selected bubble (active)' => ['.selectize-control.multi .selectize-input [data-value].active']], );
<%INIT> unless ($session{'CurrentUser'}->HasRight( Object=> RT->System, Right => 'SuperUser')) { Abort(loc('This feature is only available to system administrators.')); } use Digest::MD5 'md5_hex'; my $text_threshold = 0.6; my @results; my $imgdata; my $colors; my $valid_image_types; if (not RT->Config->Get('DisableGD') and RT::StaticUtil::RequireModule("Convert::Color")) { require GD; # Always find out what GD can read... my %gd_can; for my $type (qw(Png Jpeg Gif)) { $gd_can{$type}++ if GD::Image->can("newFrom${type}Data"); } $valid_image_types = join(", ", map { uc } sort { lc $a cmp lc $b } keys %gd_can); } my $analyze_img = sub { return undef unless $valid_image_types; my $imgdata = shift; return undef unless $imgdata; # ...but only analyze the image if we have data my $img = GD::Image->new($imgdata); unless ($img) { # This has to be one damn long line because the loc() needs to be # source parsed correctly. push @results, loc("Automatically suggested theme colors aren't available for your image. This might be because you uploaded an image type that your installed version of GD doesn't support. Supported types are: [_1]. You can recompile libgd and GD.pm to include support for other image types.", $valid_image_types); return undef; } my %colors; my @wsamples; my @hsamples; if ($img->width > 200) { @wsamples = map { int($img->width*($_/200)) } (0..199); } else { @wsamples = ( 0 .. $img->width - 1 ); } if ($img->height > 200) { @hsamples = map { int($img->height*($_/200)) } (0..199); } else { @hsamples = ( 0 .. $img->height - 1 ); } for my $i (@wsamples) { for my $j (@hsamples) { my @color = $img->rgb( $img->getPixel($i,$j) ); my $hsl = Convert::Color->new('rgb:'.join(',',map { $_ / 255 } @color))->convert_to('hsl'); my $c = join(',',@color); next if $hsl->lightness < 0.1; $colors{$c} ||= { h => $hsl->hue, s => $hsl->saturation, l => $hsl->lightness, cnt => 0, c => $c}; $colors{$c}->{cnt}++; } } for (values %colors) { $_->{rank} = $_->{s} * $_->{cnt}; } my @top5 = grep { defined and $_->{'l'} and $_->{'c'} } (sort { $b->{rank} <=> $a->{rank} } values %colors)[0..5]; return \@top5; }; if (my $file_hash = _UploadedFile( 'logo-upload' )) { $colors = $analyze_img->($file_hash->{LargeContent}); my $my_system = RT::System->new( $session{CurrentUser} ); my ( $id, $msg ) = $my_system->SetAttribute( Name => "UserLogo", Description => "User-provided logo", Content => { type => $file_hash->{ContentType}, data => $file_hash->{LargeContent}, hash => md5_hex($file_hash->{LargeContent}), colors => $colors, }, ); push @results, loc("Unable to set UserLogo: [_1]", $msg) unless $id; $imgdata = $file_hash->{LargeContent}; } elsif ($ARGS{'reset_logo'}) { RT->System->DeleteAttribute('UserLogo'); } else { if (my $attr = RT->System->FirstAttribute('UserLogo')) { my $content = $attr->Content; if (ref($content) eq 'HASH') { $imgdata = $content->{data}; $colors = $content->{colors}; unless ($colors) { # No colors cached; attempt to generate them $colors = $content->{colors} = $analyze_img->($content->{data}); if ($content->{colors}) { # Found colors; update the attribute RT->System->SetAttribute( Name => "UserLogo", Description => "User-provided logo", Content => $content, ); } } } else { RT->System->DeleteAttribute('UserLogo'); } } } if ($user_css) { if ($ARGS{'reset_css'}) { RT->System->DeleteAttribute('UserCSS'); undef $user_css; } else { my ($id, $msg) = RT->System->SetAttribute( Name => "UserCSS", Description => "User-provided css", Content => $user_css ); push @results, loc("Unable to set UserCSS: [_1]", $msg) unless $id; } } if (!$user_css) { my $attr = RT->System->FirstAttribute('UserCSS'); $user_css = $attr ? $attr->Content : join( "\n\n" => map { join "\n" => "/* ". $_->[0] ." */", map { "$_ {}" } @{$_->[1]} } @sections ); } <%ARGS> $user_css => ''