Initial commit 4.0.5-3
[usit-rt.git] / share / html / Admin / Tools / Theme.html
CommitLineData
84fb5b46
MKG
1%# BEGIN BPS TAGGED BLOCK {{{
2%#
3%# COPYRIGHT:
4%#
5%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
6%# <sales@bestpractical.com>
7%#
8%# (Except where explicitly superseded by other copyright notices)
9%#
10%#
11%# LICENSE:
12%#
13%# This work is made available to you under the terms of Version 2 of
14%# the GNU General Public License. A copy of that license should have
15%# been provided with this software, but in any event can be snarfed
16%# from www.gnu.org.
17%#
18%# This work is distributed in the hope that it will be useful, but
19%# WITHOUT ANY WARRANTY; without even the implied warranty of
20%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21%# General Public License for more details.
22%#
23%# You should have received a copy of the GNU General Public License
24%# along with this program; if not, write to the Free Software
25%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26%# 02110-1301 or visit their web page on the internet at
27%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28%#
29%#
30%# CONTRIBUTION SUBMISSION POLICY:
31%#
32%# (The following paragraph is not intended to limit the rights granted
33%# to you to modify and distribute this software under the terms of
34%# the GNU General Public License and is only of importance to you if
35%# you choose to contribute your changes and enhancements to the
36%# community by submitting them to Best Practical Solutions, LLC.)
37%#
38%# By intentionally submitting any modifications, corrections or
39%# derivatives to this work, or any other work intended for use with
40%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41%# you are the copyright holder for those contributions and you grant
42%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43%# royalty-free, perpetual, license to use, copy, create derivative
44%# works based on those contributions, and sublicense and distribute
45%# those contributions and any derivatives thereof.
46%#
47%# END BPS TAGGED BLOCK }}}
48<& /Admin/Elements/Header,
49 Title => loc("Theme"),
50&>
51<& /Elements/Tabs &>
52<& /Elements/ListActions, actions => \@results &>
53
54<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/farbtastic.js"></script>
55
56<div id="simple-customize">
57<div id="upload-logo">
58 <h2>Logo</h2>
59 <& /Elements/Logo, id => 'logo-theme-editor', ShowName => 0 &>
60 <form method="POST" enctype="multipart/form-data">
61 <label for="logo-upload"><&|/l&>Upload a new logo</&>:</label>
62 <input type="file" name="logo-upload" id="logo-upload" /><br />
63 <div class="gd-support">
64% if (%gd_can) {
65 <&|/l, $valid_image_types &>Your system supports automatic color suggestions for: [_1]</&>
66% } else {
67 <&|/l&>GD is disabled or not installed. You can upload an image, but you won't get automatic color suggestions.</&>
68% }
69 </div>
70 <input name="reset_logo" value="Reset to default RT Logo" type="submit" />
71 <input type="submit" value="Upload" />
72 </form>
73</div>
74
75<div id="customize-theme">
76 <h2>Customize the RT theme</h2>
77 <ol>
78 <li>
79 <label for="section"><&|/l&>Select a section</&>:</label>
80 <select id="section"></select>
81 </li>
82 <li>
83 <div class="description"><&|/l&>Select a color for the section</&>:</div>
84% if ($colors) {
85<div class="primary-colors">
86% for (@$colors) {
87% my $fg = $_->{l} >= $text_threshold ? 'black' : 'white';
88<button type="button" class="color-template"
89 style="background-color: rgb(<% $_->{c} %>); color: <% $fg %>;">
90 <&|/l&>Text</&>
91</button>
92% }
93</div>
94% }
95 <div id="color-picker"></div>
96 </li>
97 </ol>
98</div>
99</div>
100
101<div id="custom-css">
102 <h2>Custom CSS (Advanced)</h2>
103
104 <form method="POST">
105 <textarea rows=20 id="user_css" name="user_css" wrap="off"><% $user_css %></textarea><br />
106 <input id="try" type="button" class="button" value="Try" />
107 <input id="reset" type="reset" value="Reset" type="submit" />
108 <input name="reset_css" value="Reset to default RT Theme" type="submit" />
109 <input value="Save" type="submit" />
110 </form>
111</div>
112
113<%ONCE>
114my @sections = (
115 ['Page' => ['body']],
116 ['Header' => ['div#quickbar', 'body.aileron #main-navigation #app-nav > li, body.aileron #main-navigation #app-nav > li > a, #prefs-menu > li, #prefs-menu > li > a, #logo .rtname']],
117 ['Page title' => ['div#header h1']],
118 ['Page content' => ['div#body']],
119 ['Buttons' => ['input[type="reset"], input[type="submit"], input[class="button"]']],
120 ['Button hover' => ['input[type="reset"]:hover, input[type="submit"]:hover, input[class="button"]:hover']],
121);
122</%ONCE>
123<script type="text/javascript">
124var section_css_mapping = <% JSON(\@sections) |n%>;
125
126jQuery(function($) {
127
128 jQuery.each(section_css_mapping, function(i,v){
129 $('select#section').append($("<option/>")
130 .attr('value', v[0])
131 .text(v[0]));
132 });
133
134 $("style#sitecss").text($('#user_css').val());
135 $('#try').click(function() {
136 $("style#sitecss").text($('#user_css').val());
137 });
138
139 $('#reset').click(function() {
140 setTimeout(function() {
141 $("style#sitecss").text($('#user_css').val());
142 }, 1000);
143 });
144
145 function change_color(bg, fg) {
146 var section = $('select#section').val();
147
148 var applying = jQuery.grep(section_css_mapping, function(a){ return a[0] == section })[0][1];
149 var css = $('#user_css').val();
150 if (applying) {
151 var specials = new RegExp("([.*+?|()\\[\\]{}\\\\])", "g");
152 for (var name in applying) {
153 var selector = (applying[name]).replace(specials, "\\$1");
154 var rule = new RegExp('^'+selector+'\\s*\{.*?\}', "m");
155 var newcss = "background: " + bg;
156
157 /* Don't set the text color on <body> as it affects too much */
158 if (applying[name] != "body")
159 newcss += "; color: " + fg;
160
161 /* Kill the border on the quickbar if we're styling it */
162 if (applying[name].match(/quickbar/))
163 newcss += "; border: none;"
164
165 /* Page title's text color is the selected color */
166 if (applying[name].match(/#header/))
167 newcss = "color: " + bg;
168
169 /* Nav doesn't need a background, but it wants text color */
170 if (applying[name].match(/#main-navigation/))
171 newcss = "color: " + fg;
172
173 css = css.replace(rule, applying[name]+" { "+newcss+" }");
174 }
175 }
176 $('#user_css').val(css);
177 $("style#sitecss").text(css);
178 }
179
180 $('#color-picker').farbtastic(function(color){ change_color(color, this.hsl[2] > <% $text_threshold %> ? '#000' : '#fff') });
181
182 $('button.color-template').click(function() {
183 change_color($(this).css('background-color'), $(this).css('color'));
184 });
185
186
187});
188</script>
189<%INIT>
190unless ($session{'CurrentUser'}->HasRight( Object=> RT->System, Right => 'SuperUser')) {
191 Abort(loc('This feature is only available to system administrators.'));
192}
193
194use Digest::MD5 'md5_hex';
195
196my $text_threshold = 0.6;
197my @results;
198my $imgdata;
199
200if (my $file_hash = _UploadedFile( 'logo-upload' )) {
201 my ($id, $msg) = RT->System->SetAttribute( Name => "UserLogo",
202 Description => "User-provided logo",
203 Content => {
204 type => $file_hash->{ContentType},
205 data => $file_hash->{LargeContent},
206 hash => md5_hex($file_hash->{LargeContent}),
207 } );
208 push @results, loc("Unable to set UserLogo: [_1]", $msg) unless $id;
209
210 $imgdata = $file_hash->{LargeContent};
211}
212elsif ($ARGS{'reset_logo'}) {
213 RT->System->DeleteAttribute('UserLogo');
214}
215else {
216 if (my $attr = RT->System->FirstAttribute('UserLogo')) {
217 my $content = $attr->Content;
218 if (ref($content) eq 'HASH') {
219 $imgdata = $content->{data};
220 }
221 else {
222 RT->System->DeleteAttribute('UserLogo');
223 }
224 }
225}
226
227if ($user_css) {
228 if ($ARGS{'reset_css'}) {
229 RT->System->DeleteAttribute('UserCSS');
230 undef $user_css;
231 }
232 else {
233 my ($id, $msg) = RT->System->SetAttribute( Name => "UserCSS",
234 Description => "User-provided css",
235 Content => $user_css );
236 push @results, loc("Unable to set UserCSS: [_1]", $msg) unless $id;
237 }
238}
239
240if (!$user_css) {
241 my $attr = RT->System->FirstAttribute('UserCSS');
242 $user_css = $attr ? $attr->Content : join(
243 "\n\n" => map {
244 join "\n" => "/* ". $_->[0] ." */",
245 map { "$_ {}" } @{$_->[1]}
246 } @sections
247 );
248}
249
250# XXX: move this to some other modules
251
252use List::MoreUtils qw(uniq);
253
254my $has_color_analyzer = eval { require Convert::Color; 1 };
255my $colors;
256my %gd_can;
257my $valid_image_types;
258
259if (not RT->Config->Get('DisableGD') and $has_color_analyzer) {
260 require GD;
261
262 # Always find out what GD can read...
263 for my $type (qw(Png Jpeg Gif)) {
264 $gd_can{$type}++ if GD::Image->can("newFrom${type}Data");
265 }
266 $valid_image_types = join(", ", map { uc } sort { lc $a cmp lc $b } keys %gd_can);
267
268 # ...but only analyze the image if we have data
269 if ($imgdata) {
270 if ( my $img = GD::Image->new($imgdata) ) {
271 $colors = analyze_img($img);
272 }
273 else {
274 # This has to be one damn long line because the loc() needs to be
275 # source parsed correctly.
276 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);
277 }
278 }
279}
280
281sub analyze_img {
282 my $img = shift;
283 my $color;
284
285 for my $i (0..$img->width-1) {
286 for my $j (0..$img->height-1) {
287 my @color = $img->rgb( $img->getPixel($i,$j) );
288 my $hsl = Convert::Color->new('rgb:'.join(',',map { $_ / 255 } @color))->convert_to('hsl');
289 my $c = join(',',@color);
290 next if $hsl->lightness < 0.1;
291 $color->{$c} ||= { h => $hsl->hue, s => $hsl->saturation, l => $hsl->lightness, cnt => 0, c => $c};
292 $color->{$c}->{cnt}++;
293 }
294 }
295
296 for (values %$color) {
297 $_->{rank} = $_->{s} * $_->{cnt};
298 }
299 my @top5 = grep { defined and $_->{'l'} and $_->{'c'} }
300 (sort { $b->{rank} <=> $a->{rank} } values %$color)[0..5];
301 if ((scalar uniq map {$_->{rank}} @top5) == 1) {
302 warn "bad";
303 }
304 return \@top5;
305}
306</%INIT>
307<%ARGS>
308$user_css => ''
309</%ARGS>